Value Objects for Gnumed Clinical Items: 1. Introduction: The scope of "item" can be descripted as a "clinically meaningful snippet of data". Clinical items contain a group of fields that are always accessed together, typically through specific database views (eg. allergy items are accessed through v_i18n_patient_allergies database view). Each item is retrieved and stored as a dictionary of pairs field name -> value. Working with multiple items requires dealing with lists of such dictionaries, wich can be difficult and little object oriented. A value object is a class that groups related attributes/fields, forming a composite value. This class is used as the return type of a business method and provides an easy to deal with, object oriented way of abstract/represent and work with any kind of clinical item. Clinical items need not correspond to clin_root_item descendants 1:1. They should also encapsulate validation code if that needs to be done outside the database. The basic design rules of Gnumed Value Objects are: - Instances DO EXIST in the database - Each instance does NOT verify FK targets existence - Each clinical item knows how to store updatable data and fetch all its attibutes in/for the respective base tables (eg allergy) - Creation of new entries in the database is NOT supported - Lazy fetching of fields is NOT supported * It is either/or. If you can instantiate an instance all data for that instance (typically one row each from a few tables forming a particular view). Lazy fetching/caching is done at the next level, say gmClinicalRecord. Eventually we may end up interjecting fully generic cTable classes inbetween the business objects and the tables. Those might then cache individual rows *independant of* clinical context (but then need an age-based? eviction mechanism). Instances of each clinical item VO class would be created through factory methods in gmClinicalRecord class. 2. Design: a) cClinItem: Is the base class of all Gnumed clinical items. Most heavy lifting is done generically in this class. Fields: pk: clinical item primary key (eg. 'id', from 'allergy' for cAllergy), used and required for fetching/updating all other item attributes. _payload: list containing the values of all item fields in an ORDERED way. That order is NOT guaranteed to stay stable. It can change due to changes in PostgreSQL code, database schema, _cmd_fetch_payload statement... so it's important always USE _idx to access fields - and even that is subject to schema changes. eg: [1, 5, 14, 5, 5, 11, 'Substance A', None, None, 'Penicillin', None, 'developed urticaria/dyspnoe this morning, eg. 12h after first tablet', , , 1, 'allergy', 'Reaction Y'] _idx: dictionary that maps field name -> order index in _payload list eg: {'id_item': 1, 'reaction': 11, 'substance': 6, 'id_episode': 4, 'generics': 8, 'id_patient': 2, 'generic_specific': 12, 'atc_code': 10, 'definite': 13, 'id_encounter': 5, 'comment': 16, 'substance_code': 7, 'id_health_issue': 3, 'id_type': 14, 'allergene': 9, 'type': 15, 'id': 0} _is_modified: boolean flag that indicates if any field of the item has been modified. Methods: __init__(self, aPKey = None, **kwargs): constructor, taking primary key 'aPKe' as argument. **kwargs accepts any number of keyword arguments. The corresponding keyword argument dict is passed to _pre_init() and _post_init(). __del__(self): clean up code. __str__(self): raw string representation, useful for diagnostic dumps (_payload string representation). __getitem__(self, attribute): accessor method for object attributes. __setitem__(self, attribute, value): setter method for object attributes. If a non-updatable field is being modified, KeyError exception is raised. It's a programming error and should be caught as soon as possible. Both __get/setitem__() also facilitate introspection and self-documentation. _pre_init(self, **kwargs): invoked very early in cClinItem.__init__(). _post_init(self, **kwargs): invoked very late in cClinItem.__init(). Both _pre/post_init() allow modifying __init__ behaviour, eg. finding the primary key from other arguments get_fields(self): retrieves a list containing all backend fetched field names (both updatable and not). get_updatable_fields(self): retrieves a list containing updatable field names eg: ['substance', 'substance_code', 'generics', 'allergene', 'atc_code', 'id_type', 'generic_specific', 'definite', 'reaction'] refetch_payload(self): fetchs all the attributes from backend. This can be used to force updates after a DB level notify. If the load goes right, True is returned. If the fetch operation couldn't be performed, False is returned. Tipically, False will be returned if refetch is invoked after the instance was programatically modified. So as to not lose changes, it's important to check for changes via _is_modified field. save_payload(self): persists all the attributes if some of them have been modified (if _is_modified == True). b) Concrete items: Clinical item derived classes (eg. cAllergy) all but defines class level variables that will be accessed by cClinItem to perform all the value object tasks. _cmd_fetch_payload: query string that will be passed to gmPG.run_ro_query(). It must retrieve all column names along its values. eg: """select * from v_i18n_patient_allergies where id=%s""" _updatable_fields: list of updatable fields. _cmds_store_payload: It's important to perform row locking before writting operation. It is implemented as a *list* of cmds that will be passed to gmPG.run_commit(). self._payload values will be added in a parameter dict, hence the SQL cmds need to use the %(arg_name)s substitution format and arg_name must bean updatable field. eg: """select 1 from allergy where id=%(id)s for update""", """update allergy set substance=%(substance)s, substance_code=%(substance_code)s ... where id=%(id)s""" 3. Usage: a) General usage: allg = cAllergy(pk): instantiates a new allergy item, which will be found and retrieved from backed by its primary key 'pk'. print allg: dumps every attribute value attVal = vacc[att]: retrieves the value for attribute 'att' and asigns it to 'attVal' variable allg[att] = attVal: sets the attribute 'att' as 'attVal' value, if it's effectively updatable. If any non-updatable field is being modified, an exception is raised. allg.save_payload(): will persist the all attributes if any of them was modified. allg.get_fields(): yields a list of attributes allg.get_updatable_fields(): yields a list of updatable attributes alg.refetch_payload(): refresh attributes by reloading them from backend b) Example usage: allg = cAllergy(1) print allg attVal = allg['substance'] allg['substance'] = 'Substance X' allg.save_payload() allg.get_fields() allg.get_updatable_fields() alg.refetch_payload() 4. Development status: cClinItem: Done cHealthIssue: Done cEpisode: Done cEncounter: Done cVaccination: Done cLabRequest: Done cLabResult: Done cAllergy: Done cReferral: Pending cRequest: Pending cPrescription: Pending cDiagnosis: Pending. Under analysis cDrug: Pending. Under analysis cCurrentlyPrescribedDrug: Pending. Under analysis