Mercurial > hgweb > health
view tryton/health/health.py @ 5039:1e982753bd69
health_services: fix bug #61877: Error when I open health service report when no institution is defined
| author | Luis Falcon <falcon@gnuhealth.org> |
|---|---|
| date | Wed, 26 Jan 2022 19:34:43 +0000 |
| parents | cff3f4768c9d |
| children | 3369af2e2622 |
line wrap: on
line source
############################################################################## # # GNU Health HMIS: The Free Health and Hospital Information System # Copyright (C) 2008-2022 Luis Falcon <falcon@gnuhealth.org> # Copyright (C) 2011-2022 GNU Solidario <health@gnusolidario.org> # Copyright (C) 2015 Cédric Krier # Copyright (C) 2014-2015 Chris Zimmerman <siv@riseup.net> # # The GNU Health HMIS component is part of the GNU Health project # www.gnuhealth.org # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # ############################################################################## import platform import os import string import random import pytz from dateutil.relativedelta import relativedelta from datetime import datetime, timedelta, date from urllib.parse import urlencode from urllib.parse import urlunparse from collections import OrderedDict from io import BytesIO from uuid import uuid4 from sql import Literal, Join from trytond.model import (ModelView, ModelSingleton, ModelSQL, MultiValueMixin, fields, Unique, tree) from trytond.wizard import Wizard, StateAction, StateView, Button from trytond.transaction import Transaction from trytond.pyson import Eval, Not, Bool, PYSONEncoder, Equal, And, Or from trytond.pool import Pool from trytond.rpc import RPC from trytond.i18n import gettext from .exceptions import ( WrongDateofBirth, DateHealedBeforeDx, EndTreatmentDateBeforeStart, MedEndDateBeforeStart, NextDoseBeforeFirst, DrugPregnancySafetyCheck, EvaluationEndBeforeStart, MustBeAPerson, NoAssociatedHealthProfessional, DupOfficialName, FedAccountMismatch, BirthCertDateMismatch, CanNotModifyVaccination ) from .core import (get_institution, compute_age_from_dates, get_health_professional) try: from PIL import Image except ImportError: Image = None __all__ = [ 'OperationalArea', 'OperationalSector', 'Occupation', 'Ethnicity', 'DomiciliaryUnit', 'FederationCountryConfig', 'BirthCertificate', 'DeathCertificate', 'Party', 'ContactMechanism', 'PersonName', 'PartyAddress', 'DrugDoseUnits', 'MedicationFrequency', 'DrugForm', 'DrugRoute', 'MedicalSpecialty', 'HealthInstitution', 'HealthInstitutionSpecialties', 'HealthInstitutionOperationalSector', 'HealthInstitutionO2M', 'HospitalBuilding', 'HospitalUnit', 'HospitalOR', 'HospitalWard', 'HospitalBed', 'HealthProfessional', 'HealthProfessionalSpecialties', 'PhysicianSP', 'Family', 'FamilyMember', 'MedicamentCategory', 'Medicament', 'ImmunizationSchedule', 'ImmunizationScheduleLine', 'ImmunizationScheduleDose', 'PathologyCategory', 'PathologyGroup', 'Pathology', 'DiseaseMembers', 'ProcedureCode', 'BirthCertExtraInfo', 'DeathCertExtraInfo', 'DeathUnderlyingCondition', 'InsurancePlan', 'Insurance', 'AlternativePersonID', 'Product', 'PatientData', 'PatientDiseaseInfo', 'Appointment', 'AppointmentReport', 'OpenAppointmentReportStart', 'OpenAppointmentReport', 'PatientPrescriptionOrder', 'PrescriptionLine', 'PatientMedication', 'PatientVaccination', 'PatientEvaluation', 'Directions', 'SecondaryCondition', 'DiagnosticHypothesis', 'SignsAndSymptoms', 'PatientECG', 'ProductTemplate', 'PageOfLife', 'Commands', 'Modules', 'Help'] class DomiciliaryUnit(ModelSQL, ModelView): 'Domiciliary Unit' __name__ = 'gnuhealth.du' def get_parent(self, subdivision): # Recursively get the parent subdivisions if (subdivision.parent): return str(subdivision.rec_name) + '\n' + \ str(self.get_parent(subdivision.parent)) else: return subdivision.rec_name def get_du_address(self, name): du_addr = '' # Street if (self.address_street): du_addr = str(self.address_street) + ' ' + \ str(self.address_street_number) + "\n" # Grab the parent subdivisions if (self.address_subdivision): du_addr = du_addr + \ str(self.get_parent(subdivision=self.address_subdivision)) # Zip Code if (self.address_zip): du_addr = du_addr + " - " + self.address_zip # Country if (self.address_country): du_addr = du_addr + "\n" + self.address_country.rec_name return du_addr name = fields.Char('Code', required=True) desc = fields.Char('Desc') address_street = fields.Char('Street') address_street_number = fields.Integer('Number') address_street_bis = fields.Char('Apartment') address_district = fields.Char( 'District', help="Neighborhood, Village, Barrio....") address_municipality = fields.Char( 'Municipality', help="Municipality, Township, county ..") address_city = fields.Char('City') address_zip = fields.Char('Zip Code') address_country = fields.Many2One( 'country.country', 'Country', help='Country') address_subdivision = fields.Many2One( 'country.subdivision', 'Subdivision', domain=[('country', '=', Eval('address_country'))], depends=['address_country']) operational_sector = fields.Many2One( 'gnuhealth.operational_sector', 'Operational Sector') picture = fields.Binary('Picture') latitude = fields.Numeric('Latitude', digits=(3, 14)) longitude = fields.Numeric('Longitude', digits=(4, 14)) altitude = fields.Integer('Altitude', help="Altitude in meters") urladdr = fields.Char( 'OSM Map', help="Locates the DU on the Open Street Map by default") # Text Representation address_repr = fields.Function(fields.Text("DU Address"), 'get_du_address') # Infrastructure dwelling = fields.Selection([ (None, ''), ('single_house', 'Single / Detached House'), ('apartment', 'Apartment'), ('townhouse', 'Townhouse'), ('factory', 'Factory'), ('building', 'Building'), ('mobilehome', 'Mobile House'), ], 'Type', sort=False) materials = fields.Selection([ (None, ''), ('concrete', 'Concrete'), ('adobe', 'Adobe'), ('wood', 'Wood'), ('mud', 'Mud / Straw'), ('stone', 'Stone'), ], 'Material', sort=False) roof_type = fields.Selection([ (None, ''), ('concrete', 'Concrete'), ('adobe', 'Adobe'), ('wood', 'Wood'), ('mud', 'Mud'), ('thatch', 'Thatched'), ('stone', 'Stone'), ], 'Roof', sort=False) total_surface = fields.Integer('Surface', help="Surface in sq. meters") bedrooms = fields.Integer('Bedrooms') bathrooms = fields.Integer('Bathrooms') housing = fields.Selection([ (None, ''), ('0', 'Shanty, deficient sanitary conditions'), ('1', 'Small, crowded but with good sanitary conditions'), ('2', 'Comfortable and good sanitary conditions'), ('3', 'Roomy and excellent sanitary conditions'), ('4', 'Luxury and excellent sanitary conditions'), ], 'Conditions', help="Housing and sanitary living conditions", sort=False) sewers = fields.Boolean('Sanitary Sewers') water = fields.Boolean('Running Water') trash = fields.Boolean('Trash recollection') electricity = fields.Boolean('Electrical supply') gas = fields.Boolean('Gas supply') telephone = fields.Boolean('Telephone') television = fields.Boolean('Television') internet = fields.Boolean('Internet') members = fields.One2Many('party.party', 'du', 'Members', readonly=True) @fields.depends('latitude', 'longitude', 'address_street', 'address_street_number', 'address_district', 'address_municipality', 'address_city', 'address_zip', 'address_subdivision', 'address_country') def on_change_with_urladdr(self): # Generates the URL to be used in OpenStreetMap # If latitude and longitude are known, use them. # Otherwise, use street, municipality, city, and so on. parts = OrderedDict() parts['scheme'] = 'http' parts['netloc'] = '' parts['path'] = '' parts['params'] = '' parts['query'] = '' parts['fragment'] = '' if (self.latitude and self.longitude): parts['netloc'] = 'openstreetmap.org' parts['path'] = '/' parts['query'] = urlencode({'mlat': self.latitude, 'mlon': self.longitude}) else: state = country = postalcode = city = municipality = \ street = number = '' if self.address_street_number is not None: number = str(self.address_street_number) if self.address_street: street = self.address_street if self.address_municipality: municipality = self.address_municipality if self.address_city: city = self.address_city if self.address_zip: postalcode = self.address_zip if self.address_subdivision: state = self.address_subdivision.name if self.address_country: country = self.address_country.code parts['netloc'] = 'nominatim.openstreetmap.org' parts['path'] = 'search' parts['query'] = urlencode( {'street': ' '.join([number, street]).strip(), 'county': municipality, 'city': city, 'state': state, 'postalcode': postalcode, 'country': country}) return urlunparse(list(parts.values())) # Show the resulting Address representation in realtime @fields.depends( 'address_street', 'address_subdivision', 'address_street_number', 'address_country') def on_change_with_address_repr(self): return self.get_du_address(name=None) # Use the value from the Federation country for newly created DUs @staticmethod def default_address_country(): Fedcountry = Pool().get('gnuhealth.federation.country.config')(1) if (Fedcountry and Fedcountry.country): return int(Fedcountry.country) @classmethod def __setup__(cls): super(DomiciliaryUnit, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('code_uniq', Unique(t, t.name), 'The Domiciliary Unit must be unique !') ] class FederationCountryConfig(ModelSingleton, ModelSQL, ModelView): 'Federation Account Country' __name__ = 'gnuhealth.federation.country.config' country = fields.Many2One( 'country.country', 'Country', required=True, help="Country code to be use as the prefix for the Federation" "ID account.") code = fields.Function(fields.Char('Code'), 'get_country_code') use_citizenship = fields.Boolean( 'Use Citizenship', help="If this option is set\n" "the default country code will be the person citizenship.\n" "This feature is reserved for future use") def get_country_code(self, name): return self.country.code3 class Party(ModelSQL, ModelView): __name__ = 'party.party' def person_age(self, name): return compute_age_from_dates( self.dob, self.deceased, self.dod, self.gender, name, None) def get_du_address(self, name): if (self.du): return self.du.address_repr person_names = fields.One2Many( 'gnuhealth.person_name', 'party', 'Person Names', states={'invisible': Not(Bool(Eval('is_person')))}) name_representation = fields.Selection([ (None, ''), ('pgfs', 'Prefix Given Family, Suffix'), ('gf', 'Given Family'), ('fg', 'Family, Given'), ('cjk', 'CJK: Family+Given'), ], 'Name Representation', sort=False, states={'invisible': Not(Bool(Eval('is_person')))}) activation_date = fields.Date( 'Activation date', help='Date of activation of the party') federation_account = fields.Char( 'Federation ID', help='Federation Account', states={'invisible': Not(Bool(Eval('is_person')))}) ref = fields.Char( 'PUID', help='Person Unique Identifier', states={'invisible': Not(Bool(Eval('is_person')))}) unidentified = fields.Boolean( 'Unidentified', help='Patient is currently unidentified', states={'invisible': Not(Bool(Eval('is_person')))}) is_person = fields.Boolean( 'Person', help='Check if the party is a person.') is_patient = fields.Boolean( 'Patient', states={'invisible': Not(Bool(Eval('is_person')))}, help='Check if the party is a patient') is_healthprof = fields.Boolean( 'Health Prof', states={'invisible': Not(Bool(Eval('is_person')))}, help='Check if the party is a health professional') is_institution = fields.Boolean( 'Institution', help='Check if the party is a Health Care Institution') is_insurance_company = fields.Boolean( 'Insurance Company', help='Check if the party is an Insurance Company') is_pharmacy = fields.Boolean( 'Pharmacy', help='Check if the party is a Pharmacy') lastname = fields.Char( 'Family names', help='Family or last names', states={'invisible': Not(Bool(Eval('is_person')))}) dob = fields.Date('DoB', help='Date of Birth') age = fields.Function(fields.Char('Age'), 'person_age') gender = fields.Selection([ (None, ''), ('m', 'Male'), ('f', 'Female'), ('nb', 'Non-binary'), ('other', 'Other'), ('nd', 'Non disclosed'), ('u', 'Unknown') ], 'Gender', states={'required': Bool(Eval('is_person'))}) photo = fields.Binary('Picture') ethnic_group = fields.Many2One('gnuhealth.ethnicity', 'Ethnicity') marital_status = fields.Selection([ (None, ''), ('s', 'Single'), ('m', 'Married'), ('c', 'Concubinage'), ('w', 'Widowed'), ('d', 'Divorced'), ('x', 'Separated'), ], 'Marital Status', sort=False) citizenship = fields.Many2One( 'country.country', 'Citizenship', help='Country of Citizenship') residence = fields.Many2One( 'country.country', 'Residence', help='Country of Residence') alternative_identification = fields.Boolean( 'Alternative IDs', help='Other types of ' 'identification, not the official PUID . ' 'Examples : Passport, foreign ID,..') alternative_ids = fields.One2Many( 'gnuhealth.person_alternative_identification', 'name', 'Alternative IDs', states={'invisible': Not(Bool(Eval('alternative_identification')))}) insurance = fields.One2Many( 'gnuhealth.insurance', 'name', 'Insurances', help="Insurance Plans associated to this party") internal_user = fields.Many2One( 'res.user', 'Internal User', help='In GNU Health is the user (person) ' 'that logins. When the' ' party is a person, it will be the user' ' that maps the party.', states={ 'invisible': Not(Bool(Eval('is_person'))), }) insurance_company_type = fields.Selection([ (None, ''), ('state', 'State'), ('labour_union', 'Labour Union / Syndical'), ('private', 'Private'), ], 'Insurance Type', select=True) insurance_plan_ids = fields.One2Many( 'gnuhealth.insurance.plan', 'company', 'Insurance Plans') du = fields.Many2One('gnuhealth.du', 'DU', help="Domiciliary Unit") du_address = fields.Function( fields.Text( 'Main address', help="Main Address, based on the associated DU"), 'get_du_address') birth_certificate = fields.Many2One( 'gnuhealth.birth_certificate', 'Birth Certificate', readonly=True) deceased = fields.Boolean( 'Deceased', readonly=True, help='The information is updated from the Death Certificate', states={'invisible': Not(Bool(Eval('deceased')))}) dod = fields.Function(fields.DateTime( 'Date of Death', states={ 'invisible': Not(Bool(Eval('deceased'))), }, depends=['deceased']), 'get_dod') death_certificate = fields.Many2One( 'gnuhealth.death_certificate', 'Death Certificate', readonly=True) mother = fields.Function( fields.Many2One( 'party.party', 'Mother', help="Mother from the Birth Certificate"), 'get_mother') father = fields.Function( fields.Many2One( 'party.party', 'Father', help="Father from the Birth Certificate"), 'get_father') fed_country = fields.Char( 'Prefix', help="3-letter Country code" "in ISO 3166-1 alpha-3 standard that will become the prefix" "of the federation account. The following user-assigned codes" "ranges can be also used" "AAA to AAZ, QMA to QZZ, XAA to XZZ, and ZZA to ZZZ." "For example XXX is unidentified nationality and XXB is a refugee." "By default, it will use the code of the emiting institution country" "Refer to the GNU Health manual for further information", states={'invisible': Not(Bool(Eval('is_person')))}) def get_mother(self, name): if (self.birth_certificate and self.birth_certificate.mother): return self.birth_certificate.mother.id def get_father(self, name): if (self.birth_certificate and self.birth_certificate.father): return self.birth_certificate.father.id def get_dod(self, name): if (self.deceased and self.death_certificate): return self.death_certificate.dod @staticmethod def default_fed_country(): Fedcountry = Pool().get('gnuhealth.federation.country.config')(1) if (Fedcountry and Fedcountry.country): return Fedcountry.code # Use the Federation country as default value for citizenship # and residence for newly created people @staticmethod def default_citizenship(): Fedcountry = Pool().get('gnuhealth.federation.country.config')(1) if (Fedcountry and Fedcountry.country): return int(Fedcountry.country) @staticmethod def default_residence(): Fedcountry = Pool().get('gnuhealth.federation.country.config')(1) if (Fedcountry and Fedcountry.country): return int(Fedcountry.country) @staticmethod def default_activation_date(): return date.today() @classmethod def generate_puid(cls): # Add a default random string in the ref field. # The STRSIZE constant provides the length of the PUID # The format of the PUID is XXXNNNXXX # By default, this field will be used only if nothing is entered STRSIZE = 9 puid = '' for x in range(STRSIZE): if (x < 3 or x > 5): puid = puid + random.choice(string.ascii_uppercase) else: puid = puid + random.choice(string.digits) return puid @classmethod def convert_photo(cls, data): if data and Image: image = Image.open(BytesIO(data)) image.thumbnail((200, 200), Image.ANTIALIAS) data = BytesIO() image.save(data, image.format) data = fields.Binary.cast(data.getvalue()) return data @classmethod def write(cls, *args): actions = iter(args) args = [] for parties, vals in zip(actions, actions): given_name = family_name = '' vals = vals.copy() person_id = parties[0].id # We set the value to None to make the fields that have a # unique constraint get the NULL value at PostgreSQL level, and not # the value '' coming from the client if vals.get('ref') == '': vals['ref'] = None if vals.get('federation_account') == '': vals['federation_account'] = None if 'photo' in vals: vals['photo'] = cls.convert_photo(vals['photo']) if ('name' in vals) or ('lastname' in vals): given_name = family_name = '' if 'name' in vals: given_name = vals['name'] if 'lastname' in vals: family_name = vals['lastname'] if parties[0].is_person: cls.update_person_official_name(person_id, given_name, family_name) args.append(parties) args.append(vals) return super(Party, cls).write(*args) @classmethod def update_person_official_name(cls, person_id, given_name, family_name): # Create / update the official PersonName entry with the Given / Family # names from the main entry field. person = [] Pname = Pool().get('gnuhealth.person_name') officialnames = Pname.search( [("party", "=", person_id), ("use", "=", 'official')],) # If no official name found, create a new record if not (officialnames): values = { 'party': person_id, 'use': 'official', } if given_name: values['given'] = given_name if family_name: values['family'] = family_name person.append(values) Pname.create(person) # Found a related official name record, then # update official Person Name(s) when modified in main form else: official_rec = [] official_rec.append(officialnames[0]) values = {'use': 'official'} if given_name: values['given'] = given_name if family_name: values['family'] = family_name Pname.write(official_rec, values) @classmethod def create(cls, vlist): vlist = [x.copy() for x in vlist] tmp_act = cls.generate_puid() for values in vlist: if not values.get('ref'): if values.get('federation_account'): # Strip the country code from the fed account # and pass it to the local PUID values['ref'] = values.get('federation_account')[3:] else: values['ref'] = tmp_act if 'unidentified' in values and values['unidentified']: values['ref'] = 'NN-' + values.get('ref') if 'is_person' in values and not values['is_person']: values['ref'] = 'NP-' + values['ref'] # Generate the Federation account ID # with the ISO 3166-1 alpha-3 as prefix # using the same code as in the newly created PUID # If the person is NN or there is no country assigned # use the prefix XXX if not values.get('federation_account') and \ values.get('is_person'): federation_account = tmp_act values['federation_account'] = values['fed_country'] + \ federation_account # Set the value to None to make the fields that have a # unique constraint get the NULL value at PostgreSQL level, and not # the value '' coming from the client if values.get('federation_account') == '': values['federation_account'] = None # Generate internal code if not values.get('code'): # Use the company name . Initially, use the name # since the company hasn't been created yet. suffix = Transaction().context.get('company.rec_name') \ or values['name'] # Generate the party code in the form of # "UUID-" . Where company is the name of the Health # Institution. # # The field "code" is the one that is used in distributed # environments, with multiple GNU Health instances across # a country / region values['code'] = '%s-%s' % (uuid4(), suffix) values.setdefault('addresses', None) if 'photo' in values: values['photo'] = cls.convert_photo(values['photo']) # If the party is a physical person, # add new PersonName record with the given and family name # as the official name if (values.get('is_person')): if ('name' in values) or ('lastname' in values): official_name = [] given_name = family_name = '' if 'name' in values: given_name = values['name'] if 'lastname' in values: family_name = values['lastname'] official_name.append(('create', [{ 'use': 'official', 'given': given_name, 'family': family_name, }])) values['person_names'] = official_name return super(Party, cls).create(vlist) @classmethod def __setup__(cls): super(Party, cls).__setup__() t = cls.__table__() cls._sql_constraints += [ ('ref_uniq', Unique(t, t.ref), 'The PUID must be unique'), ('internal_user_uniq', Unique(t, t.internal_user), 'This internal user is already assigned to a party'), ('federation_account_uniq', Unique(t, t.federation_account), 'The Federation Account must be unique'), ] cls._order.insert(0, ('lastname', 'ASC')) cls._order.insert(1, ('name', 'ASC')) # Sort to be used when called from other models. cls._order_name = 'lastname' def get_rec_name(self, name): # Display name on the following sequence # 1 - Oficial Name from PersonName with the name representation # If not offficial name : # 2 - Last name, First name if self.person_names: prefix = given = family = suffix = '' for pname in self.person_names: if pname.prefix: prefix = pname.prefix + ' ' if pname.suffix: suffix = ', ' + pname.suffix given = pname.given or '' family = pname.family or '' res = '' if pname.use == 'official': if self.name_representation == 'pgfs': res = prefix + given + ' ' + family + suffix if self.name_representation == 'gf': if pname.family: family = ' ' + pname.family res = given + family if self.name_representation == 'fg': if pname.family: family = pname.family + ', ' res = family + given if self.name_representation == 'cjk': if pname.family: family = pname.family res = family + given if not self.name_representation: # Default value if family: return family + ', ' + given else: return given return res if self.lastname: return self.lastname + ', ' + self.name else: return self.name @classmethod def search_rec_name(cls, name, clause): """ Search for the name, lastname, PUID, any alternative IDs, and any family and / or given name from the person_names """ if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' return [bool_op, ('ref',) + tuple(clause[1:]), ('alternative_ids.code',) + tuple(clause[1:]), ('federation_account',) + tuple(clause[1:]), ('contact_mechanisms.value',) + tuple(clause[1:]), ('person_names.family',) + tuple(clause[1:]), ('person_names.given',) + tuple(clause[1:]), ('name',) + tuple(clause[1:]), ('lastname',) + tuple(clause[1:]), ] @fields.depends('is_person', 'is_patient', 'is_healthprof') def on_change_with_is_person(self): # Set is_person if the party is a health professional or a patient if (self.is_healthprof or self.is_patient or self.is_person): return True @fields.depends('du') def on_change_with_du_address(self): if (self.du): return self.get_du_address(name=None) @classmethod def validate(cls, parties): super(Party, cls).validate(parties) for party in parties: party.check_person() party.validate_official_name() party.validate_dob() def validate_dob(self): """ Check that the date is sane * The person is alive * Non-negative years, months or days * < 200 (future generations :) ) """ if (self.dob): years, months, days = \ compute_age_from_dates(self.dob, self.deceased, self.dod, self.gender, 'raw_age', None) if (not self.deceased): if (years < 0 or months < 0 or days < 0) or years > 200: raise WrongDateofBirth( gettext('health.msg_wrong_date_of_birth') ) def check_person(self): # Verify that health professional and patient # are unchecked when is_person is False if not self.is_person and (self.is_patient or self.is_healthprof): raise MustBeAPerson( gettext('health.msg_must_be_a_person') ) def validate_official_name(self): # Only allow one official name on the party name Pname = Pool().get('gnuhealth.person_name') officialnames = Pname.search_count( [("party", "=", self.id), ("use", "=", 'official')],) if (officialnames > 1): raise DupOfficialName( gettext('health.msg_dup_official_name') ) @classmethod def view_attributes(cls): # Hide the group holding all the demographics when the party is not # a person return super(Party, cls).view_attributes() + [ ('//group[@id="person_details"]', 'states', { 'invisible': ~Eval('is_person'), })] class PageOfLife(ModelSQL, ModelView): 'Page of Life' __name__ = 'gnuhealth.pol' def age_at_page(self): if (self.person.dob and self.page_date): return compute_age_from_dates( self.person.dob, None, None, None, 'age', self.page_date.date()) person = fields.Many2One( 'party.party', 'Person', required=True, domain=[ ('is_person', '=', True), ], states={'readonly': Eval('id', 0) > 0}, help="Person") federation_account = fields.Char( 'Account', required=True, help="Federation Account") page = fields.Char('Page', help="Page of Life") page_date = fields.DateTime( 'Date', help="Date of this page / event. By default it takes" "the current date / time when creating the event," "but it can be a past date " "and it can also be empty, or used in combination with" "the person age at that moment") age = fields.Char('Age', help="Age at the moment of this page of life") page_type = fields.Selection([ (None, ''), ('biographical', 'Biographical'), ('medical', 'Medical'), ('demographical', 'Demographical'), ('social', 'Social'), ], 'Page type', sort=False, required=True) medical_context = fields.Selection([ (None, ''), ('health_condition', 'Health Condition'), ('encounter', 'Encounter'), ('procedure', 'Procedure'), ('self_monitoring', 'Self monitoring'), ('immunization', 'Immunization'), ('prescription', 'Prescription'), ('surgery', 'Surgery'), ('hospitalization', 'Hospitalization'), ('lab', 'lab'), ('dx_imaging', 'Dx Imaging'), ('genetics', 'Genetics'), ('family', 'Family history'), ('birth', 'Birth'), ('death', 'Death'), ], 'Medical Context', sort=False, states={'required': Equal(Eval('page_type'), 'medical'), 'invisible': Not(Equal(Eval('page_type'), 'medical')) }) social_context = fields.Selection([ (None, ''), ('social_gradient', 'Social Gradient / Equity'), ('stress', 'Stress'), ('early_life_development', 'Early life development'), ('social_exclusion', 'Social exclusion'), ('working_conditions', 'Working conditions'), ('education', 'Education'), ('physical_environment', 'Physical environment'), ('unemployment', 'unemployment'), ('social_support', 'Social Support'), ('addiction', 'Addiction'), ('food', 'Food'), ('transport', 'transport'), ('health_services', 'Health services'), ('uninsured', 'uninsured'), ('family_functionality', 'Family functionality'), ('family_violence', 'Family violence'), ('bullying', 'Bullying'), ('war', 'War'), ], 'Social Context', sort=False, states={'required': Equal(Eval('page_type'), 'social'), 'invisible': Not(Equal(Eval('page_type'), 'social')) }) relevance = fields.Selection([ (None, ''), ('informational', 'Informational'), ('important', 'Important'), ('critical', 'Critical'), ], 'Relevance', sort=False, required=True) health_condition = fields.Many2One( 'gnuhealth.pathology', 'Health Condition') health_condition_code = fields.Char("Code") health_condition_text = fields.Char("Health Condition") procedure = fields.Many2One( 'gnuhealth.procedure', 'Procedure', help="Procedure code") procedure_text = fields.Char("Procedure") procedure_code = fields.Char("Procedure Code") gene = fields.Char( "Gene", help="Gene / protein", states={'invisible': Not(Equal(Eval('medical_context'), 'genetics'))}) natural_variant = fields.Char( "Natural variant", states={'invisible': Not(Equal(Eval('medical_context'), 'genetics'))}) phenotype = fields.Char( "Phenotype", states={'invisible': Not(Equal(Eval('medical_context'), 'genetics'))}) summary = fields.Char("Summary") measurements = fields.Char("Measurements", readonly=True, help="Automatically included measurements" " such vital signs, HR, RR and anthropometrics") info = fields.Text("Extended Information") institution = fields.Many2One('gnuhealth.institution', 'Institution') node = fields.Char("Node") author = fields.Char("Author") author_acct = fields.Char("Author account") @staticmethod def default_institution(): return get_institution() @staticmethod def default_page(): return str(uuid4()) @staticmethod def default_page_date(): return datetime.now() # Get the text representation of the health condition @fields.depends('health_condition') def on_change_health_condition(self): if (self.health_condition): self.health_condition_text = self.health_condition.name self.health_condition_code = self.health_condition.code # Get the text representation of the procedure @fields.depends('procedure') def on_change_procedure(self): if (self.procedure): self.procedure_text = self.procedure.description self.procedure_code = self.procedure.name # Get the text representation of the institution @fields.depends('institution') def on_change_institution(self): if (self.institution): self.node = str(self.institution.name.name) # Retrieve the federation account @fields.depends('person') def on_change_person(self): if (self.person): self.federation_account = self.person.federation_account @fields.depends('person', 'page_date') def on_change_with_age(self): if (self.person and self.page_date): computed_age = self.age_at_page() return computed_age @classmethod def validate(cls, pages): super(PageOfLife, cls).validate(pages) for page in pages: page.validate_account() def validate_account(self): if (self.person.federation_account != self.federation_account): raise FedAccountMismatch( gettext('health.msg_fed_account_mismatch') ) @classmethod def create(cls, vlist): Party = Pool().get('party.party') vlist = [x.copy() for x in vlist] for values in vlist: if values.get('federation_account') and not values.get('person'): party, = Party.search( [("name", "=", values.get('federation_account'))], limit=1) values['person'] = party if 'page' not in values: values['page'] = str(uuid4()) return super(PageOfLife, cls).create(vlist) @classmethod def __setup__(cls): super(PageOfLife, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('page_uniq', Unique(t, t.page), 'The Page must be unique !'), ] cls._order.insert(0, ('page_date', 'DESC')) class ContactMechanism(ModelSQL, ModelView): __name__ = 'party.contact_mechanism' emergency = fields.Boolean('Emergency', select=True) remarks = fields.Char('Remarks', help="Enter the name of the contact" " or other remarks") class PersonName(ModelSQL, ModelView): "Person Name" __name__ = 'gnuhealth.person_name' """ We are using the concept of HumanName on HL7 FHIR http://www.hl7.org/implement/standards/fhir/datatypes.html#HumanName """ party = fields.Many2One('party.party', 'Person', domain=[('is_person', '=', True)], help="Related party (person)") use = fields.Selection([ (None, ''), ('official', 'Official'), ('usual', 'Usual'), ('nickname', 'Nickname'), ('maiden', 'Maiden'), ('anonymous', 'Anonymous'), ('temp', 'Temp'), ('old', 'old'), ], 'Use', sort=False, required=True) family = fields.Char('Family', help="Family / Surname.") given = fields.Char( 'Given', help="Given / First name. May include middle name", required=True) prefix = fields.Selection([ (None, ''), ('Mr', 'Mr'), ('Mrs', 'Mrs'), ('Miss', 'Miss'), ('Dr', 'Dr'), ], 'Prefix', sort=False) suffix = fields.Char('Suffix') date_from = fields.Date('From') date_to = fields.Date('To') class PartyAddress(ModelSQL, ModelView): 'Party Address' __name__ = 'party.address' relationship = fields.Char( 'Relationship', help='Include the relationship with the person - friend, co-worker,' ' brother,...') relative_id = fields.Many2One( 'party.party', 'Contact', domain=[('is_person', '=', True)], help='Include link to the relative') is_school = fields.Boolean( "School", help="Check this box to mark the school address") is_work = fields.Boolean( "Work", help="Check this box to mark the work address") class DrugDoseUnits(ModelSQL, ModelView): 'Drug Dose Unit' __name__ = 'gnuhealth.dose.unit' name = fields.Char('Unit', required=True, select=True, translate=True) desc = fields.Char('Description', translate=True) @classmethod def __setup__(cls): super(DrugDoseUnits, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name), 'The Unit must be unique !'), ] class MedicationFrequency(ModelSQL, ModelView): 'Medication Common Frequencies' __name__ = 'gnuhealth.medication.dosage' name = fields.Char( 'Frequency', required=True, select=True, translate=True, help='Common frequency name') code = fields.Char( 'Code', required=True, help='Dosage Code,for example: SNOMED 229798009 = 3 times per day.' 'Please use CAPITAL LETTERS and no spaces') abbreviation = fields.Char( 'Abbreviation', help='Dosage abbreviation, such as tid in the US or tds in the UK') @classmethod def __setup__(cls): super(MedicationFrequency, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name), 'The Unit must be unique !'), ('code_uniq', Unique(t, t.code), 'The CODE must be unique !'), ] class DrugForm(ModelSQL, ModelView): 'Drug Form' __name__ = 'gnuhealth.drug.form' name = fields.Char('Form', required=True, select=True, translate=True) code = fields.Char('Code', required=True, help="Please use CAPITAL LETTERS and no spaces") @classmethod def __setup__(cls): super(DrugForm, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name), 'The Unit must be unique !'), ('code_uniq', Unique(t, t.code), 'The CODE must be unique !'), ] class DrugRoute(ModelSQL, ModelView): 'Drug Administration Route' __name__ = 'gnuhealth.drug.route' name = fields.Char('Route', required=True, select=True, translate=True) code = fields.Char('Code', required=True, help="Please use CAPITAL LETTERS and no spaces") @classmethod def __setup__(cls): super(DrugRoute, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name), 'The Name must be unique !'), ('code_uniq', Unique(t, t.code), 'The CODE must be unique !'), ] class Occupation(ModelSQL, ModelView): 'Occupation' __name__ = 'gnuhealth.occupation' name = fields.Char('Name', required=True, translate=True) code = fields.Char('Code', required=True, help="Please use CAPITAL LETTERS and no spaces") @classmethod def __setup__(cls): super(Occupation, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name), 'The Name must be unique !'), ('code_uniq', Unique(t, t.code), 'The CODE must be unique !'), ] class Ethnicity(ModelSQL, ModelView): 'Ethnicity' __name__ = 'gnuhealth.ethnicity' name = fields.Char('Name', required=True, translate=True) code = fields.Char('Code', required=True, help="Please use CAPITAL LETTERS and no spaces") notes = fields.Char('Notes') @classmethod def __setup__(cls): super(Ethnicity, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name), 'The Name must be unique !'), ('code_uniq', Unique(t, t.code), 'The CODE must be unique !'), ] class OperationalArea(ModelSQL, ModelView): 'Operational Area' __name__ = 'gnuhealth.operational_area' name = fields.Char( 'Name', required=True, help='Operational Area of the city or region') operational_sector = fields.One2Many( 'gnuhealth.operational_sector', 'operational_area', 'Operational Sector', readonly=True) info = fields.Text('Extra Information') @classmethod def __setup__(cls): super(OperationalArea, cls).__setup__() t = cls.__table__() cls._sql_constraints += [ ('name_uniq', Unique(t, t.name), 'The operational area must be unique !'), ] class OperationalSector(ModelSQL, ModelView): 'Operational Sector' __name__ = 'gnuhealth.operational_sector' name = fields.Char( 'Op. Sector', required=True, help='Region included in an operational area') operational_area = fields.Many2One( 'gnuhealth.operational_area', 'Operational Area') info = fields.Text('Extra Information') @classmethod def __setup__(cls): super(OperationalSector, cls).__setup__() t = cls.__table__() cls._sql_constraints += [ ('name_uniq', Unique(t, t.name, t.operational_area), 'The operational sector must be unique in each' ' operational area!'), ] # HEALTH INSTITUTION class HealthInstitution(ModelSQL, ModelView): 'Health Institution' __name__ = 'gnuhealth.institution' name = fields.Many2One( 'party.party', 'Institution', domain=[('is_institution', '=', True)], help='Party Associated to this Health Institution', required=True, states={'readonly': Bool(Eval('name'))}) code = fields.Char('Code', required=True, help="Institution code") picture = fields.Binary('Picture') institution_type = fields.Selection(( (None, ''), ('doctor_office', 'Doctor office'), ('primary_care', 'Primary Care Center'), ('clinic', 'Clinic'), ('hospital', 'General Hospital'), ('specialized', 'Specialized Hospital'), ('nursing_home', 'Nursing Home'), ('hospice', 'Hospice'), ('rural', 'Rural facility'), ), 'Type', required=True, sort=False) beds = fields.Integer("Beds") operating_room = fields.Boolean("Operating Room", help="Check this box if the institution" " has operating rooms",) or_number = fields.Integer( "ORs", states={'invisible': Not(Bool(Eval('operating_room')))}) public_level = fields.Selection(( (None, ''), ('private', 'Private'), ('public', 'Public'), ('mixed', 'Private - State'), ), 'Public Level', required=True, sort=False) teaching = fields.Boolean("Teaching", help="Mark if this is a" " teaching institution") heliport = fields.Boolean("Heliport") trauma_center = fields.Boolean("Trauma Center") trauma_level = fields.Selection(( (None, ''), ('one', 'Level I'), ('two', 'Level II'), ('three', 'Level III'), ('four', 'Level IV'), ('five', 'Level V'), ), 'Trauma Level', sort=False, states={'invisible': Not(Bool(Eval('trauma_center')))}) extra_info = fields.Text("Extra Info") def get_rec_name(self, name): if self.name: return self.name.name @classmethod def __setup__(cls): super(HealthInstitution, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name), 'This Institution already exists !'), ('code_uniq', Unique(t, t.code), 'This CODE already exists !'), ] class HealthInstitutionSpecialties(ModelSQL, ModelView): 'Health Institution Specialties' __name__ = 'gnuhealth.institution.specialties' name = fields.Many2One('gnuhealth.institution', 'Institution', required=True) specialty = fields.Many2One('gnuhealth.specialty', 'Specialty', required=True) def get_rec_name(self, name): if self.specialty: return self.specialty.name @classmethod def __setup__(cls): super(HealthInstitutionSpecialties, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_sp_uniq', Unique(t, t.name, t.specialty), 'The Specialty already exists for this institution'), ] class HealthInstitutionOperationalSector(ModelSQL, ModelView): 'Operational Sectors covered by Institution' __name__ = 'gnuhealth.institution.operationalsector' name = fields.Many2One('gnuhealth.institution', 'Institution', required=True) operational_sector = fields.Many2One('gnuhealth.operational_sector', 'Operational Sector', required=True) @classmethod def __setup__(cls): super(HealthInstitutionOperationalSector, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_os_uniq', Unique(t, t.name, t.operational_sector), 'The Operational Sector already exists for this institution'), ] class HealthInstitutionO2M(ModelSQL, ModelView): 'Health Institution' __name__ = 'gnuhealth.institution' # Add Specialties to the Health Institution specialties = fields.One2Many( 'gnuhealth.institution.specialties', 'name', 'Specialties', help="Specialties Provided in this Health Institution") main_specialty = fields.Many2One( 'gnuhealth.institution.specialties', 'Specialty', domain=[('name', '=', Eval('id'))], depends=['specialties', 'institution_type', 'id'], help="Choose the speciality in the case of Specialized Hospitals" " or where this center excels", # Allow to select the institution specialty only if the record already # exists states={'required': And(Eval('institution_type') == 'specialized', Eval('id', 0) > 0), 'readonly': Eval('id', 0) < 0}) # Add Specialties to the Health Institution operational_sectors = fields.One2Many( 'gnuhealth.institution.operationalsector', 'name', 'Operational Sector', help="Operational Sectors covered by this institution") # HEALTH CENTER / HOSPITAL INFRASTRUCTURE class HospitalBuilding(ModelSQL, ModelView): 'Hospital Building' __name__ = 'gnuhealth.hospital.building' name = fields.Char( 'Name', required=True, help='Name of the building within the institution') institution = fields.Many2One( 'gnuhealth.institution', 'Institution', required=True, help='Health Institution of this building') code = fields.Char('Code', required=True) extra_info = fields.Text('Extra Info') @staticmethod def default_institution(): return get_institution() @classmethod def __setup__(cls): super(HospitalBuilding, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name, t.institution), 'The Building name must be unique per Health' ' Center'), ('code_uniq', Unique(t, t.code, t.institution), 'The Building code must be unique per Health' ' Center'), ] class HospitalUnit(ModelSQL, ModelView): 'Hospital Unit' __name__ = 'gnuhealth.hospital.unit' name = fields.Char( 'Name', required=True, help='Name of the unit, eg Neonatal, Intensive Care, ...') institution = fields.Many2One( 'gnuhealth.institution', 'Institution', required=True, help='Health Institution') code = fields.Char('Code', required=True) extra_info = fields.Text('Extra Info') @staticmethod def default_institution(): return get_institution() @classmethod def __setup__(cls): super(HospitalUnit, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name, t.institution), 'The Unit NAME must be unique per Health' ' Center'), ('code_uniq', Unique(t, t.code, t.institution), 'The Unit CODE must be unique per Health' ' Center'), ] class HospitalOR(ModelSQL, ModelView): 'Operating Room' __name__ = 'gnuhealth.hospital.or' name = fields.Char( 'Name', required=True, help='Name of the Operating Room') institution = fields.Many2One( 'gnuhealth.institution', 'Institution', required=True, help='Health Institution') building = fields.Many2One( 'gnuhealth.hospital.building', 'Building', domain=[('institution', '=', Eval('institution'))], depends=['institution'], select=True) unit = fields.Many2One( 'gnuhealth.hospital.unit', 'Unit', domain=[('institution', '=', Eval('institution'))], depends=['institution']) extra_info = fields.Text('Extra Info') state = fields.Selection(( (None, ''), ('free', 'Free'), ('confirmed', 'Confirmed'), ('occupied', 'Occupied'), ('na', 'Not available'), ), 'Status', readonly=True, sort=False) @staticmethod def default_institution(): return get_institution() @staticmethod def default_state(): return 'free' @classmethod def __setup__(cls): super(HospitalOR, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name, t.institution), 'The Operating Room Name must be unique per Health' ' Center'), ] class HospitalWard(ModelSQL, ModelView): 'Hospital Ward' __name__ = 'gnuhealth.hospital.ward' name = fields.Char('Name', required=True, help='Ward / Room code') institution = fields.Many2One( 'gnuhealth.institution', 'Institution', required=True, help='Health Institution') building = fields.Many2One( 'gnuhealth.hospital.building', 'Building', domain=[('institution', '=', Eval('institution'))], depends=['institution']) floor = fields.Integer('Floor Number') unit = fields.Many2One('gnuhealth.hospital.unit', 'Unit', domain=[('institution', '=', Eval('institution'))], depends=['institution']) private = fields.Boolean( 'Private', help='Check this option for private room') bio_hazard = fields.Boolean( 'Bio Hazard', help='Check this option if there is biological hazard') number_of_beds = fields.Integer( 'Number of beds', help='Number of patients per ward') telephone = fields.Boolean('Telephone access') ac = fields.Boolean('Air Conditioning') private_bathroom = fields.Boolean('Private Bathroom') guest_sofa = fields.Boolean('Guest sofa-bed') tv = fields.Boolean('Television') internet = fields.Boolean('Internet Access') refrigerator = fields.Boolean('Refrigerator') microwave = fields.Boolean('Microwave') gender = fields.Selection(( (None, ''), ('men', 'Men Ward'), ('women', 'Women Ward'), ('unisex', 'Unisex'), ), 'Gender', required=True, sort=False) state = fields.Selection(( (None, ''), ('beds_available', 'Beds available'), ('full', 'Full'), ('na', 'Not available'), ), 'Status', sort=False) extra_info = fields.Text('Extra Info') @staticmethod def default_gender(): return 'unisex' @staticmethod def default_number_of_beds(): return 1 @staticmethod def default_institution(): return get_institution() @classmethod def __setup__(cls): super(HospitalWard, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name, t.institution), 'The Ward / Room Name must be unique per Health' ' Center'), ] class HospitalBed(ModelSQL, ModelView): 'Hospital Bed' __name__ = 'gnuhealth.hospital.bed' _rec_name = 'telephone_number' name = fields.Many2One( 'product.product', 'Bed', required=True, domain=[('is_bed', '=', True)], help='Bed Number') institution = fields.Many2One( 'gnuhealth.institution', 'Institution', required=True, help='Health Institution') ward = fields.Many2One( 'gnuhealth.hospital.ward', 'Ward', domain=[('institution', '=', Eval('institution'))], depends=['institution'], help='Ward or room') bed_type = fields.Selection(( (None, ''), ('gatch', 'Gatch Bed'), ('electric', 'Electric'), ('stretcher', 'Stretcher'), ('low', 'Low Bed'), ('low_air_loss', 'Low Air Loss'), ('circo_electric', 'Circo Electric'), ('clinitron', 'Clinitron'), ), 'Bed Type', required=True, sort=False) telephone_number = fields.Char( 'Telephone Number', help='Telephone number / Extension') extra_info = fields.Text('Extra Info') state = fields.Selection(( (None, ''), ('free', 'Free'), ('reserved', 'Reserved'), ('occupied', 'Occupied'), ('to_clean', 'Needs cleaning'), ('na', 'Not available'), ), 'Status', readonly=True, sort=False) @staticmethod def default_bed_type(): return 'gatch' @staticmethod def default_state(): return 'free' @staticmethod def default_institution(): return get_institution() def get_rec_name(self, name): if self.name: return self.name.name @classmethod def search_rec_name(cls, name, clause): return [('name',) + tuple(clause[1:])] @classmethod def __setup__(cls): super(HospitalBed, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name, t.institution), 'The Bed must be unique per Health Center'), ] # Show fix button when is in state "needs cleaning" or "NA" cls._buttons.update({ 'fix_bed': { 'invisible': Or(Equal(Eval('state'), 'free'), Equal(Eval('state'), 'occupied'), Equal(Eval('state'), 'reserved') )}, }), @classmethod @ModelView.button def fix_bed(cls, beds): cls.write(beds, {'state': 'free'}) class MedicalSpecialty(ModelSQL, ModelView): 'Medical Specialty' __name__ = 'gnuhealth.specialty' name = fields.Char( 'Specialty', required=True, translate=True, help='ie, Addiction Psychiatry') code = fields.Char('Code', required=True, help='ie, ADP. Please use CAPITAL LETTERS ' 'and no spaces') @classmethod def __setup__(cls): t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name), 'The Specialty must be unique !'), ('code_uniq', Unique(t, t.code), 'The CODE must be unique !'), ] super(MedicalSpecialty, cls).__setup__() class HealthProfessional(ModelSQL, ModelView): 'Health Professional' __name__ = 'gnuhealth.healthprofessional' name = fields.Many2One( 'party.party', 'Health Professional', required=True, domain=[ ('is_healthprof', '=', True), ('is_person', '=', True), ], help='Health Professional\'s Name, from the partner list') institution = fields.Many2One( 'gnuhealth.institution', 'Institution', help='Main institution where she/he works') code = fields.Char('LICENSE ID', help='License ID') specialties = fields.One2Many( 'gnuhealth.hp_specialty', 'name', 'Specialties') info = fields.Text('Extra info') puid = fields.Function( fields.Char('PUID', help="Person Unique Identifier"), 'get_hp_puid', searcher='search_hp_puid') active = fields.Boolean('Active', select=True) @staticmethod def default_active(): return True def get_hp_puid(self, name): return self.name.ref @staticmethod def default_institution(): return get_institution() @classmethod def search_hp_puid(cls, name, clause): res = [] value = clause[2] res.append(('name.ref', clause[1], value)) return res @classmethod def __setup__(cls): super(HealthProfessional, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('hp_uniq', Unique(t, t.name), 'The health professional must be unique'), ('code_uniq', Unique(t, t.code), 'The LICENSE ID must be unique'), ] def get_rec_name(self, name): if self.name: res = self.name.rec_name return res class HealthProfessionalSpecialties(ModelSQL, ModelView): 'Health Professional Specialties' __name__ = 'gnuhealth.hp_specialty' name = fields.Many2One('gnuhealth.healthprofessional', 'Health Professional', required=True) specialty = fields.Many2One( 'gnuhealth.specialty', 'Specialty', required=True, help='Specialty Code') def get_rec_name(self, name): return self.specialty.name @classmethod def search_rec_name(cls, name, clause): if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' return [bool_op, ('specialty',) + tuple(clause[1:]), ] @classmethod def __setup__(cls): super(HealthProfessionalSpecialties, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name, t.specialty), 'This specialty is already assigned to the Health Professional'), ] class PhysicianSP(ModelSQL, ModelView): # Add Main Specialty field after from the Health Professional Speciality 'Health Professional' __name__ = 'gnuhealth.healthprofessional' main_specialty = fields.Many2One( 'gnuhealth.hp_specialty', 'Main Specialty', domain=[('name', '=', Eval('id'))], states={'readonly': Eval('id', 0) < 0}, depends=['id']) class Family(ModelSQL, ModelView): 'Family' __name__ = 'gnuhealth.family' name = fields.Char('Family', required=True, help='Family code') members = fields.One2Many( 'gnuhealth.family_member', 'name', 'Family Members') info = fields.Text('Extra Information') @classmethod def __setup__(cls): super(Family, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name), 'The Family Code must be unique !'), ] class FamilyMember(ModelSQL, ModelView): 'Family Member' __name__ = 'gnuhealth.family_member' name = fields.Many2One( 'gnuhealth.family', 'Family', required=True, readonly=True, help='Family code') party = fields.Many2One( 'party.party', 'Party', required=True, domain=[('is_person', '=', True)], help='Family Member') role = fields.Char('Role', help='Father, Mother, sibbling...') # Use the template as in Product category. class MedicamentCategory(tree(separator=' / '), ModelSQL, ModelView): 'Medicament Category' __name__ = 'gnuhealth.medicament.category' name = fields.Char('Name', required=True, translate=True) parent = fields.Many2One( 'gnuhealth.medicament.category', 'Parent', select=True) childs = fields.One2Many( 'gnuhealth.medicament.category', 'parent', string='Children') @classmethod def __setup__(cls): super(MedicamentCategory, cls).__setup__() cls._order.insert(0, ('name', 'ASC')) def get_rec_name(self, name): if self.parent: return self.parent.get_rec_name(name) + ' / ' + self.name else: return self.name class Medicament(ModelSQL, ModelView): 'Medicament' __name__ = 'gnuhealth.medicament' name = fields.Many2One( 'product.product', 'Product', required=True, domain=[('is_medicament', '=', True)], help='Product Name') active_component = fields.Char( 'Active component', translate=True, help='Active Component') category = fields.Many2One( 'gnuhealth.medicament.category', 'Category', select=True) therapeutic_action = fields.Char( 'Therapeutic effect', help='Therapeutic action') composition = fields.Text('Composition', help='Components') indications = fields.Text('Indication', help='Indications') strength = fields.Float( 'Strength', help='Amount of medication (eg, 250 mg) per dose') unit = fields.Many2One( 'gnuhealth.dose.unit', 'dose unit', help='Unit of measure for the medication to be taken') route = fields.Many2One( 'gnuhealth.drug.route', 'Administration Route', help='Drug administration route code.') form = fields.Many2One( 'gnuhealth.drug.form', 'Form', help='Drug form, such as tablet, suspension, liquid ..') sol_conc = fields.Float( 'Concentration', help='Solution drug concentration') sol_conc_unit = fields.Many2One( 'gnuhealth.dose.unit', 'Unit', help='Unit of the drug concentration') sol_vol = fields.Float( 'Volume', help='Solution concentration volume') sol_vol_unit = fields.Many2One( 'gnuhealth.dose.unit', 'Unit', help='Unit of the solution volume') dosage = fields.Text('Dosage Instructions', help='Dosage / Indications') overdosage = fields.Text('Overdosage', help='Overdosage') pregnancy_warning = fields.Boolean( 'Pregnancy Warning', help='The drug represents risk to pregnancy or lactancy') pregnancy = fields.Text( 'Pregnancy and Lactancy', help='Warnings for Pregnant Women') pregnancy_category = fields.Selection([ (None, ''), ('A', 'A'), ('B', 'B'), ('C', 'C'), ('D', 'D'), ('X', 'X'), ('N', 'N'), ], 'Pregnancy Category', help='** FDA Pregnancy Categories ***\n' 'CATEGORY A :Adequate and well-controlled human studies have failed' ' to demonstrate a risk to the fetus in the first trimester of' ' pregnancy (and there is no evidence of risk in later' ' trimesters).\n\n' 'CATEGORY B : Animal reproduction studies have failed todemonstrate a' ' risk to the fetus and there are no adequate and well-controlled' ' studies in pregnant women OR Animal studies have shown an adverse' ' effect, but adequate and well-controlled studies in pregnant women' ' have failed to demonstrate a risk to the fetus in any' ' trimester.\n\n' 'CATEGORY C : Animal reproduction studies have shown an adverse' ' effect on the fetus and there are no adequate and well-controlled' ' studies in humans, but potential benefits may warrant use of the' ' drug in pregnant women despite potential risks. \n\n ' 'CATEGORY D : There is positive evidence of human fetal risk based' ' on adverse reaction data from investigational or marketing' ' experience or studies in humans, but potential benefits may warrant' ' use of the drug in pregnant women despite potential risks.\n\n' 'CATEGORY X : Studies in animals or humans have demonstrated fetal' ' abnormalities and/or there is positive evidence of human fetal risk' ' based on adverse reaction data from investigational or marketing' ' experience, and the risks involved in use of the drug in pregnant' ' women clearly outweigh potential benefits.\n\n' 'CATEGORY N : Not yet classified') presentation = fields.Text('Presentation', help='Packaging') adverse_reaction = fields.Text('Adverse Reactions') storage = fields.Text('Storage Conditions') is_vaccine = fields.Boolean('Vaccine') notes = fields.Text('Extra Info') active = fields.Boolean('Active', select=True) @staticmethod def default_active(): return True # Show the icon depending on the pregnancy category pregnancy_cat_icon = \ fields.Function(fields.Char('Preg. Cat. Icon'), 'get_preg_cat_icon') def get_preg_cat_icon(self, name): if (self.pregnancy_category == 'X'): return 'gnuhealth-stop' if (self.pregnancy_category == 'D' or self.pregnancy_category == "C"): return 'gnuhealth-warning' def get_rec_name(self, name): return self.name.name # Allow to search by name, active component or category @classmethod def search_rec_name(cls, name, clause): if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' return [bool_op, ('name',) + tuple(clause[1:]), ('active_component',) + tuple(clause[1:]), ('category',) + tuple(clause[1:]), ] @classmethod def check_xml_record(cls, records, values): return True class ImmunizationScheduleDose(ModelSQL, ModelView): 'Immunization Schedule Dose' __name__ = 'gnuhealth.immunization_schedule_dose' vaccine = fields.Many2One( 'gnuhealth.immunization_schedule_line', 'Vaccine', required=True, help='Vaccine Name') dose_number = fields.Integer('Dose', required=True) age_dose = fields.Integer('Age', required=True) age_unit = fields.Selection([ (None, ''), ('days', 'days'), ('weeks', 'weeks'), ('months', 'months'), ('years', 'years'), ], 'Time Unit', required=True) remarks = fields.Char('Remarks') sched = fields.Function( fields.Char('Schedule'), 'get_dose_schedule', searcher='search_dose_schedule') def get_dose_schedule(self, name): return self.vaccine.sched.sched @classmethod def search_dose_schedule(cls, name, clause): res = [] value = clause[2] res.append(('vaccine.sched.sched', clause[1], value)) return res @classmethod def __setup__(cls): super(ImmunizationScheduleDose, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('number_uniq', Unique(t, t.dose_number, t.vaccine), 'The dose number must be unique for this vaccine'), ] cls._order.insert(0, ('vaccine', 'ASC')) cls._order.insert(1, ('dose_number', 'ASC')) class ImmunizationScheduleLine(ModelSQL, ModelView): 'Immunization Schedule Line' __name__ = 'gnuhealth.immunization_schedule_line' sched = fields.Many2One( 'gnuhealth.immunization_schedule', 'Schedule', required=True, help='Schedule Name') vaccine = fields.Many2One( 'gnuhealth.medicament', 'Vaccine', required=True, domain=[('is_vaccine', '=', True)], help='Vaccine Name') scope = fields.Selection([ (None, ''), ('systematic', 'Systematic'), ('recommended', 'Recommended'), ('highrisk', 'Risk groups'), ], 'Scope', sort=False) remarks = fields.Char('Remarks') doses = fields.One2Many('gnuhealth.immunization_schedule_dose', 'vaccine', 'Doses') def get_rec_name(self, name): return (self.vaccine.name.name) @staticmethod def default_scope(): return 'systematic' class ImmunizationSchedule(ModelSQL, ModelView): 'Immunization Schedule' __name__ = 'gnuhealth.immunization_schedule' sched = fields.Char('Code', help="Code for this immunization schedule", required=True) country = fields.Many2One('country.country', 'Country') year = fields.Integer('Year') active = fields.Boolean('Active') vaccines = fields.One2Many('gnuhealth.immunization_schedule_line', 'sched', 'Vaccines') desc = fields.Char('Description', help="Short Description for this immunization schedule", required=True) def get_rec_name(self, name): return (self.sched) @staticmethod def default_active(): return True @classmethod def __setup__(cls): super(ImmunizationSchedule, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('sched_uniq', Unique(t, t.sched), 'The schedule code must be unique'), ] class PathologyCategory(tree(separator=' / '), ModelSQL, ModelView): 'Disease Categories' __name__ = 'gnuhealth.pathology.category' name = fields.Char('Category Name', required=True, translate=True) parent = fields.Many2One( 'gnuhealth.pathology.category', 'Parent Category', select=True) childs = fields.One2Many( 'gnuhealth.pathology.category', 'parent', 'Children Category') @classmethod def validate(cls, categories): super(PathologyCategory, cls).validate(categories) def get_rec_name(self, name): if self.parent: return self.parent.get_rec_name(name) + ' / ' + self.name else: return self.name @classmethod def __setup__(cls): super(PathologyCategory, cls).__setup__() t = cls.__table__() cls._sql_constraints += [ ('name_uniq', Unique(t, t.name), 'The category name must be unique'), ] cls._order.insert(0, ('name', 'ASC')) class PathologyGroup(ModelSQL, ModelView): 'Pathology Groups' __name__ = 'gnuhealth.pathology.group' name = fields.Char( 'Name', required=True, translate=True, help='Group name') code = fields.Char( 'Code', required=True, help='for example MDG6 code will contain the Millennium Development' ' Goals # 6 diseases : Tuberculosis, Malaria and HIV/AIDS') desc = fields.Char('Short Description', required=True) info = fields.Text('Detailed information') members = fields.One2Many('gnuhealth.disease_group.members', 'disease_group', 'Members', readonly=True) @classmethod def __setup__(cls): super(PathologyGroup, cls).__setup__() t = cls.__table__() cls._sql_constraints += [ ('code_uniq', Unique(t, t.code), 'The Pathology Group code must be unique'), ] class Pathology(ModelSQL, ModelView): 'Health Conditions' __name__ = 'gnuhealth.pathology' name = fields.Char( 'Name', required=True, translate=True, help='Health condition name') code = fields.Char( 'Code', required=True, help='Specific Code for the Disease (eg, ICD-10)') category = fields.Many2One( 'gnuhealth.pathology.category', 'Main Category', help='Select the main category for this disease This is usually' ' associated to the standard. For instance, the chapter on the ICD' ' will be the main category for de disease') groups = fields.One2Many( 'gnuhealth.disease_group.members', 'name', 'Groups', help='Specify the groups this pathology belongs. Some' ' automated processes act upon the code of the group') chromosome = fields.Char('Affected Chromosome', help='chromosome number') protein = fields.Char( 'Protein involved', help='Name of the protein(s) affected') gene = fields.Char('Gene', help='Name of the gene(s) affected') info = fields.Text('Extra Info') uri = fields.Char('URI', help="Resource / Entity URL address") active = fields.Boolean('Active', select=True) @staticmethod def default_active(): return True # Include code + description in result def get_rec_name(self, name): return (self.code + ' : ' + self.name) # Search by the health condition code or the description @classmethod def search_rec_name(cls, name, clause): if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' return [bool_op, ('code',) + tuple(clause[1:]), ('name',) + tuple(clause[1:]), ] @classmethod def __setup__(cls): super(Pathology, cls).__setup__() t = cls.__table__() cls._sql_constraints += [ ('code_uniq', Unique(t, t.code), 'The disease code must be unique'), ] # DISEASE GROUP MEMBERS class DiseaseMembers(ModelSQL, ModelView): 'Disease group members' __name__ = 'gnuhealth.disease_group.members' name = fields.Many2One('gnuhealth.pathology', 'Condition', readonly=True) disease_group = fields.Many2One( 'gnuhealth.pathology.group', 'Group', required=True) class ProcedureCode(ModelSQL, ModelView): 'Medical Procedures' __name__ = 'gnuhealth.procedure' name = fields.Char('Code', required=True) description = fields.Char('Long Text', translate=True) # Include code + description in result def get_rec_name(self, name): return (self.name + ' : ' + self.description) # Search by the Procedure code or the description @classmethod def search_rec_name(cls, name, clause): if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' return [bool_op, ('name',) + tuple(clause[1:]), ('description',) + tuple(clause[1:]), ] # Add institution attribute AFTER registering the Health Institution # Health Professionals and underlying conditions class BirthCertExtraInfo (ModelSQL, ModelView): 'Birth Certificate' __name__ = 'gnuhealth.birth_certificate' STATES = {'readonly': Eval('state') == 'done'} institution = fields.Many2One( 'gnuhealth.institution', 'Institution', states=STATES) signed_by = fields.Many2One( 'gnuhealth.healthprofessional', 'Certifier', readonly=True, help='Person who certifies this' ' birth document', states=STATES) certification_date = fields.DateTime('Signed on', readonly=True, states=STATES) @staticmethod def default_institution(): return get_institution() @fields.depends('institution') def on_change_institution(self): country = None subdivision = None if (self.institution and self.institution.name.addresses[0].country): country = self.institution.name.addresses[0].country.id if (self.institution and self.institution.name.addresses[0]. subdivision): subdivision = self.institution.name.addresses[0].subdivision.id self.country = country self.country_subdivision = subdivision @classmethod @ModelView.button def sign(cls, certificates): pool = Pool() Person = pool.get('party.party') party = [] # Change the state of the birth certificate to "Signed" # and write the name of the certifying health professional signing_hp = get_health_professional() cls.write(certificates, { 'state': 'signed', 'signed_by': signing_hp, 'certification_date': datetime.now()}) party.append(certificates[0].name) Person.write(party, { 'birth_certificate': certificates[0].id}) class DeathCertExtraInfo (ModelSQL, ModelView): 'Death Certificate' __name__ = 'gnuhealth.death_certificate' STATES = {'readonly': Eval('state') == 'done'} institution = fields.Many2One( 'gnuhealth.institution', 'Institution', states=STATES) signed_by = fields.Many2One( 'gnuhealth.healthprofessional', 'Signed by', readonly=True, states={'invisible': Equal(Eval('state'), 'draft')}, help="Health Professional that signed the death certificate") certification_date = fields.DateTime('Certified on', readonly=True) cod = fields.Many2One( 'gnuhealth.pathology', 'Cause', required=True, help="Immediate Cause of Death", states=STATES) underlying_conditions = fields.One2Many( 'gnuhealth.death_underlying_condition', 'death_certificate', 'Underlying Conditions', help='Underlying' ' conditions that initiated the events resulting in death.' ' Please code them in sequential, chronological order', states=STATES) @staticmethod def default_institution(): return get_institution() @staticmethod def default_state(): return 'draft' @fields.depends('institution') def on_change_institution(self): country = None subdivision = None if (self.institution and self.institution.name.addresses[0].country): country = self.institution.name.addresses[0].country.id if (self.institution and self.institution.name.addresses[0]. subdivision): subdivision = self.institution.name.addresses[0].subdivision.id self.country = country self.country_subdivision = subdivision @classmethod @ModelView.button def sign(cls, certificates): pool = Pool() Person = pool.get('party.party') # Change the state of the death certificate to "Signed" # and write the name of the certifying health professional # It also set the associated party attribute deceased to True. party = [] signing_hp = get_health_professional() cls.write(certificates, { 'state': 'signed', 'signed_by': signing_hp, 'certification_date': datetime.now()}) party.append(certificates[0].name) Person.write(party, { 'deceased': True, 'death_certificate': certificates[0].id }) # UNDERLYING CONDITIONS THAT RESULT IN DEATH INCLUDED IN DEATH CERT. class DeathUnderlyingCondition(ModelSQL, ModelView): 'Underlying Conditions' __name__ = 'gnuhealth.death_underlying_condition' death_certificate = fields.Many2One( 'gnuhealth.death_certificate', 'Certificate', readonly=True) condition = fields.Many2One( 'gnuhealth.pathology', 'Condition', required=True) interval = fields.Integer('Interval', help='Approx Interval' ' onset to death', required=True) unit_of_time = fields.Selection([ (None, ''), ('minutes', 'minutes'), ('hours', 'hours'), ('days', 'days'), ('months', 'months'), ('years', 'years'), ], 'Unit', select=True, sort=False, required=True) class InsurancePlan(ModelSQL, ModelView): 'Insurance Plan' __name__ = 'gnuhealth.insurance.plan' _rec_name = 'company' name = fields.Many2One( 'product.product', 'Plan', required=True, domain=[('is_insurance_plan', '=', True)], help='Insurance company plan') company = fields.Many2One( 'party.party', 'Insurance Company', required=True, domain=[('is_insurance_company', '=', True)]) is_default = fields.Boolean( 'Default plan', help='Check if this is the default plan when assigning this insurance' ' company to a patient') notes = fields.Text('Extra info') def get_rec_name(self, name): return self.name.name class Insurance(ModelSQL, ModelView): 'Insurance' __name__ = 'gnuhealth.insurance' _rec_name = 'number' # Insurance associated to an individual name = fields.Many2One('party.party', 'Owner') number = fields.Char('Number', required=True) company = fields.Many2One( 'party.party', 'Insurance Company', required=True, select=True, domain=[('is_insurance_company', '=', True)]) member_since = fields.Date('Member since') member_exp = fields.Date('Expiration date') category = fields.Char( 'Category', help='Insurance company category') insurance_type = fields.Selection([ (None, ''), ('state', 'State'), ('labour_union', 'Labour Union / Syndical'), ('private', 'Private'), ], 'Insurance Type', select=True) plan_id = fields.Many2One( 'gnuhealth.insurance.plan', 'Plan', help='Insurance company plan', domain=[('company', '=', Eval('company'))], depends=['company']) notes = fields.Text('Extra Info') def get_rec_name(self, name): return (self.company.name + ' : ' + self.number) @classmethod def __setup__(cls): super(Insurance, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('number_uniq', Unique(t, t.number, t.company), 'The number must be unique per insurance company'), ] class AlternativePersonID (ModelSQL, ModelView): 'Alternative person ID' __name__ = 'gnuhealth.person_alternative_identification' name = fields.Many2One('party.party', 'Party', readonly=True) code = fields.Char('Code', required=True) alternative_id_type = fields.Selection( [ (None, ''), ('country_id', 'Country of origin ID'), ('passport', 'Passport'), ('medical_record', 'Medical Record'), ('ghealth_federation', 'GNU Health Federation'), ('other', 'Other'), ], 'ID type', required=True, sort=False,) expiration_date = fields.Date('Expiration date') comments = fields.Char('Comments') class BirthCertificate (ModelSQL, ModelView): 'Birth Certificate' __name__ = 'gnuhealth.birth_certificate' STATES = {'readonly': Eval('state') == 'done'} name = fields.Many2One('party.party', 'Person', required=True, domain=[('is_person', '=', True), ], states={'readonly': Eval('id', 0) > 0}) mother = fields.Many2One('party.party', 'Mother', domain=[('is_person', '=', True), ], states=STATES) father = fields.Many2One('party.party', 'Father', domain=[('is_person', '=', True), ], states=STATES) code = fields.Char('Code', required=True, states=STATES) dob = fields.Date('Date of Birth', required=True, states=STATES) observations = fields.Text('Observations', states=STATES) country = fields.Many2One('country.country', 'Country', required=True, states=STATES) country_subdivision = fields.Many2One( 'country.subdivision', 'Subdivision', domain=[('country', '=', Eval('country'))], depends=['country'], states=STATES) state = fields.Selection([ (None, ''), ('draft', 'Draft'), ('signed', 'Signed'), ('done', 'Done'), ], 'State', readonly=True, sort=False) @staticmethod def default_state(): return 'draft' @fields.depends('name') def on_change_with_dob(self): if (self.name and self.name.dob): dob = self.name.dob return dob @classmethod def __setup__(cls): super(BirthCertificate, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name), 'Certificate already exists !'), ('code_uniq', Unique(t, t.code), 'Certificate already exists !'), ] cls._buttons.update({ 'sign': {'invisible': Or(Equal(Eval('state'), 'signed'), Equal(Eval('state'), 'done'))} }) @classmethod def validate(cls, certificates): super(BirthCertificate, cls).validate(certificates) for certificate in certificates: certificate.validate_dob() def validate_dob(self): if (self.name.dob != self.dob): raise BirthCertDateMismatch( gettext('health.birth_cert_date_mismatch') ) class DeathCertificate (ModelSQL, ModelView): 'Death Certificate' __name__ = 'gnuhealth.death_certificate' STATES = {'readonly': Eval('state') == 'done'} name = fields.Many2One('party.party', 'Person', required=True, domain=[('is_person', '=', True), ], states=STATES) code = fields.Char('Code', required=True, states=STATES) autopsy = fields.Boolean('Autopsy', help="Check this box " "if autopsy has been done", states=STATES) dod = fields.DateTime('Date', required=True, help="Date and time of Death", states=STATES) type_of_death = fields.Selection( [ (None, ''), ('natural', 'Natural'), ('suicide', 'Suicide'), ('homicide', 'Homicide'), ('undetermined', 'Undetermined'), ('pending_investigation', 'Pending Investigation'), ], 'Type of death', required=True, sort=False, states=STATES) place_of_death = fields.Selection( [ (None, ''), ('home', 'Home'), ('work', 'Work'), ('public_place', 'Public place'), ('health_center', 'Health Center'), ], 'Place', required=True, sort=False, states=STATES) operational_sector = fields.Many2One( 'gnuhealth.operational_sector', 'Op. Sector', states=STATES) du = fields.Many2One( 'gnuhealth.du', 'DU', help="Domiciliary Unit", states=STATES) place_details = fields.Char('Details', states=STATES) country = fields.Many2One('country.country', 'Country', required=True, states=STATES) country_subdivision = fields.Many2One( 'country.subdivision', 'Subdivision', domain=[('country', '=', Eval('country'))], depends=['country'], states=STATES) age = fields.Function(fields.Char('Age'), 'get_age_at_death') observations = fields.Text('Observations', states=STATES) state = fields.Selection([ (None, ''), ('draft', 'Draft'), ('signed', 'Signed'), ('done', 'Done'), ], 'State', readonly=True, sort=False) @staticmethod def default_state(): return 'draft' @classmethod def __setup__(cls): super(DeathCertificate, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name), 'Certificate already exists !'), ('code_uniq', Unique(t, t.code), 'Certificate already exists !'), ] cls._buttons.update({ 'sign': {'invisible': Or(Equal(Eval('state'), 'signed'), Equal(Eval('state'), 'done'))} }) def get_age_at_death(self, name): if (self.name.dob): delta = relativedelta(self.dod, self.name.dob) years_months_days = str(delta.years) + 'y ' \ + str(delta.months) + 'm ' \ + str(delta.days) + 'd' else: years_months_days = None return years_months_days class Product(ModelSQL, ModelView): 'Product' __name__ = 'product.product' is_medicament = fields.Boolean( 'Medicament', help='Check if the product is a medicament') is_medical_supply = fields.Boolean( 'Medical Supply', help='Check if the product is a medical supply') is_vaccine = fields.Boolean( 'Vaccine', help='Check if the product is a vaccine') is_bed = fields.Boolean( 'Bed', help='Check if the product is a bed on the gnuhealth.center') is_insurance_plan = fields.Boolean( 'Insurance Plan', help='Check if the product is an insurance plan') @classmethod def check_xml_record(cls, records, values): return True # PATIENT GENERAL INFORMATION class PatientData(ModelSQL, ModelView): 'Patient related information' __name__ = 'gnuhealth.patient' def patient_critical_summary(self, name): # Patient Critical Information Summary # The information will be shown in the front page allergies = "" other_conditions = "" conditions = [] for disease in self.diseases: for member in disease.pathology.groups: '''Retrieve patient allergies''' if (member.disease_group.name == "ALLERGIC"): if disease.pathology.name not in conditions: allergies = f"{allergies}" \ f"{disease.pathology.rec_name}\n" conditions.append(disease.pathology.rec_name) '''Retrieve patient other relevant conditions ''' '''Chronic and active''' if (disease.status == "c" or disease.is_active): if disease.pathology.name not in conditions: other_conditions = f"{other_conditions} " \ f"{disease.pathology.rec_name}\n" return allergies + other_conditions name = fields.Many2One( 'party.party', 'Patient', required=True, domain=[ ('is_patient', '=', True), ('is_person', '=', True), ], states={'readonly': Eval('id', 0) > 0}, help="Person associated to this patient") lastname = fields.Function( fields.Char('Lastname'), 'get_patient_lastname', searcher='search_patient_lastname') puid = fields.Function( fields.Char('PUID', help="Person Unique Identifier"), 'get_patient_puid', searcher='search_patient_puid') family = fields.Many2One( 'gnuhealth.family', 'Family', help='Family Code') current_insurance = fields.Many2One( 'gnuhealth.insurance', 'Insurance', domain=[('name', '=', Eval('name'))], depends=['name'], help='Insurance information. You may choose from the different' ' insurances belonging to the patient') current_address = fields.Many2One( 'party.address', 'Temp. Addr', domain=[('party', '=', Eval('name'))], depends=['name'], help="Use this address for temporary contact information. For example \ if the patient is on vacation, you can put the hotel address. \ In the case of a Domiciliary Unit, just link it to the name of the \ contact in the address form.") primary_care_doctor = fields.Many2One( 'gnuhealth.healthprofessional', 'GP', help='Current General Practitioner / Family Doctor') # Removed in 2.0 . PHOTO It's now a functional field # Retrieves the information from the party. photo = fields.Function(fields.Binary('Picture'), 'get_patient_photo') # Removed in 2.0 . DOB It's now a functional field # Retrieves the information from the party. # dob = fields.Date('DoB', help='Date of Birth') dob = fields.Function(fields.Date('DoB'), 'get_patient_dob') age = fields.Function(fields.Char('Age'), 'get_patient_age') gender = fields.Function(fields.Selection([ (None, ''), ('m', 'Male'), ('f', 'Female'), ('f-m', 'Female -> Male'), ('m-f', 'Male -> Female'), ], 'Gender'), 'get_patient_gender') biological_sex = fields.Selection([ (None, ''), ('m', 'Male'), ('f', 'Female'), ], 'Biological Sex', help="Biological sex. By defaults it takes the value" " from the neonatal information") # Removed in 2.0 . MARITAL STATUS It's now a functional field # Retrieves the information from the party. marital_status = fields.Function( fields.Selection([ (None, ''), ('s', 'Single'), ('m', 'Married'), ('c', 'Concubinage'), ('w', 'Widowed'), ('d', 'Divorced'), ('x', 'Separated'), ], 'Marital Status', sort=False, help="Marital Status"), 'get_patient_marital_status') blood_type = fields.Selection([ (None, ''), ('A', 'A'), ('B', 'B'), ('AB', 'AB'), ('O', 'O'), ], 'Blood Type', sort=False) rh = fields.Selection([ (None, ''), ('+', '+'), ('-', '-'), ], 'Rh') hb = fields.Selection([ (None, ''), ('aa', 'AA'), ('as', 'AS'), ('ss', 'SS'), ('sc', 'SC'), ('cc', 'CC'), ('athal', 'A-THAL'), ('bthal', 'B-THAL'), ], 'Hb', help="Clinically relevant Hemoglobin types\n" "AA = Normal Hemoglobin\n" "AS = Sickle Cell Trait\n" "SS = Sickle Cell Anemia\n" "AC = Sickle Cell Hemoglobin C Disease\n" "CC = Hemoglobin C Disease\n" "A-THAL = A Thalassemia groups\n" "B-THAL = B Thalassemia groups\n" ) vaccinations = fields.One2Many( 'gnuhealth.vaccination', 'name', 'Vaccinations', readonly=True) medications = fields.One2Many( 'gnuhealth.patient.medication', 'name', 'Medications') diseases = fields.One2Many( 'gnuhealth.patient.disease', 'name', 'Conditions', readonly=True) critical_summary = fields.Function(fields.Text( 'Important health conditions related to this patient', help='Automated summary of patient important health conditions ' 'other critical information'), 'patient_critical_summary') critical_info = fields.Text( 'Free text information not included in the automatic summary', help='Write any important information on the patient\'s condition,' ' surgeries, allergies, ...') general_info = fields.Text( 'General Information', help='General information about the patient') deceased = fields.Function(fields.Boolean('Deceased'), 'check_is_alive') dod = fields.Function(fields.DateTime( 'Date of Death', states={ 'invisible': Not(Bool(Eval('deceased'))), }, depends=['deceased']), 'get_dod') cod = fields.Function(fields.Many2One( 'gnuhealth.pathology', 'Cause of Death', states={ 'invisible': Not(Bool(Eval('deceased'))), }, depends=['deceased']), 'get_cod') childbearing_age = fields.Function( fields.Boolean('Potential for Childbearing'), 'get_childbearing_age') appointments = fields.One2Many( 'gnuhealth.appointment', 'patient', 'Appointments') active = fields.Boolean('Active', select=True) @staticmethod def default_active(): return True @classmethod def __setup__(cls): super(PatientData, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('name_uniq', Unique(t, t.name), 'The Patient already exists !'), ] cls._order.insert(0, ('name', 'ASC')) def get_patient_dob(self, name): return self.name.dob def get_patient_gender(self, name): gender = self.name.gender sex = self.biological_sex if sex: if (gender != sex): res = sex + '-' + gender else: res = gender else: res = gender return res def get_patient_photo(self, name): return self.name.photo def get_patient_puid(self, name): return self.name.ref def get_patient_marital_status(self, name): return self.name.marital_status def check_is_alive(self, name): return self.name.deceased def get_patient_age(self, name): return self.name.age def get_childbearing_age(self, name): return compute_age_from_dates( self.dob, self.deceased, self.dod, self.gender, name, None) def get_dod(self, name): if (self.deceased): return self.name.death_certificate.dod def get_cod(self, name): if (self.deceased): return self.name.death_certificate.cod.id # Show the gender upon entering the individual @fields.depends('name') def on_change_name(self): if (self.name): self.gender = self.name.gender self.age = self.name.age @classmethod def search_patient_puid(cls, name, clause): res = [] value = clause[2] res.append(('name.ref', clause[1], value)) return res def get_patient_lastname(self, name): return self.name.lastname @classmethod def search_patient_lastname(cls, name, clause): res = [] value = clause[2] res.append(('name.lastname', clause[1], value)) return res def get_rec_name(self, name): if self.name: return self.name.rec_name # Search by the patient name, lastname or PUID @classmethod def search_rec_name(cls, name, clause): if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' return [bool_op, ('puid',) + tuple(clause[1:]), ('name',) + tuple(clause[1:]), ('lastname',) + tuple(clause[1:]), ] # PATIENT CONDITIONS INFORMATION class PatientDiseaseInfo(ModelSQL, ModelView): 'Patient Conditions History' __name__ = 'gnuhealth.patient.disease' name = fields.Many2One('gnuhealth.patient', 'Patient') pathology = fields.Many2One( 'gnuhealth.pathology', 'Condition', required=True, help='Condition') disease_severity = fields.Selection([ (None, ''), ('1_mi', 'Mild'), ('2_mo', 'Moderate'), ('3_sv', 'Severe'), ], 'Severity', select=True, sort=False) is_on_treatment = fields.Boolean('Currently on Treatment') is_infectious = fields.Boolean( 'Infectious Disease', help='Check if the patient has an infectious / transmissible disease') short_comment = fields.Char( 'Remarks', help='Brief, one-line remark of the disease. Longer description will' ' go on the Extra info field') healthprof = fields.Many2One( 'gnuhealth.healthprofessional', 'Health Prof', help='Health Professional who treated or diagnosed the patient') diagnosed_date = fields.Date('Date of Diagnosis') healed_date = fields.Date('Healed') is_active = fields.Boolean('Active disease') age = fields.Integer( 'Age when diagnosed', help='Patient age at the moment of the diagnosis. Can be estimative') pregnancy_warning = fields.Boolean('Pregnancy warning') weeks_of_pregnancy = fields.Integer('Contracted in pregnancy week #') is_allergy = fields.Boolean('Allergic Disease') allergy_type = fields.Selection([ (None, ''), ('da', 'Drug Allergy'), ('fa', 'Food Allergy'), ('ma', 'Misc Allergy'), ('mc', 'Misc Contraindication'), ], 'Allergy type', select=True, sort=False) pcs_code = fields.Many2One( 'gnuhealth.procedure', 'Code', help='Procedure code, for example, ICD-10-PCS Code 7-character string') treatment_description = fields.Char('Treatment Description') date_start_treatment = fields.Date('Start', help='Start of treatment date') date_stop_treatment = fields.Date('End', help='End of treatment date') status = fields.Selection([ (None, ''), ('a', 'acute'), ('c', 'chronic'), ('u', 'unchanged'), ('h', 'healed'), ('i', 'improving'), ('w', 'worsening'), ], 'Status', select=True, sort=False) extra_info = fields.Text('Extra Info') healthprof = fields.Many2One( 'gnuhealth.healthprofessional', 'Health Prof', readonly=True, help='Health Professional') related_evaluations = fields.One2Many( 'gnuhealth.patient.evaluation', 'related_condition', 'Related Evaluations', readonly=True) institution = fields.Many2One('gnuhealth.institution', 'Institution') @staticmethod def default_institution(): return get_institution() @classmethod def __setup__(cls): super(PatientDiseaseInfo, cls).__setup__() cls._order.insert(0, ('is_active', 'DESC')) cls._order.insert(1, ('disease_severity', 'DESC')) cls._order.insert(2, ('is_infectious', 'DESC')) cls._order.insert(3, ('diagnosed_date', 'DESC')) @staticmethod def default_is_active(): return True @classmethod def validate(cls, diseases): super(PatientDiseaseInfo, cls).validate(diseases) for disease in diseases: disease.validate_disease_period() disease.validate_treatment_dates() def validate_disease_period(self): Lang = Pool().get('ir.lang') language, = Lang.search([ ('code', '=', Transaction().language), ]) if (self.healed_date and self.diagnosed_date): if (self.healed_date < self.diagnosed_date): raise DateHealedBeforeDx( gettext('health.msg_healed_before_dx', healed_date=Lang.strftime( self.healed_date, language.code, language.date), diagnosed_date=Lang.strftime( self.diagnosed_date, language.code, language.date), ) ) def validate_treatment_dates(self): Lang = Pool().get('ir.lang') language, = Lang.search([ ('code', '=', Transaction().language), ]) if (self.date_stop_treatment and self.date_start_treatment): if (self.date_stop_treatment < self.date_start_treatment): raise EndTreatmentDateBeforeStart( gettext('health.msg_end_treatment_before_start', date_stop_treatment=Lang.strftime( self.date_stop_treatment, language.code, language.date), date_start_treatment=Lang.strftime( self.date_start_treatment, language.code, language.date), ) ) # Show warning on infectious disease infectious_disease_icon = \ fields.Function(fields.Char('Infect'), 'get_infect_disease_icon') def get_infect_disease_icon(self, name): if (self.is_infectious): return 'gnuhealth-warning' @staticmethod def default_healthprof(): return get_health_professional() def get_rec_name(self, name): return self.pathology.rec_name @classmethod def create_health_condition_pol(cls, condition_info): """ Adds an entry in the person Page of Life related to this person health condition """ Pol = Pool().get('gnuhealth.pol') pol = [] def patient_age_at_dx(): age_at_dx = '' if condition_info.age: age_at_dx = str(condition_info.age) + 'y' elif (condition_info.name.dob and condition_info.diagnosed_date): age_at_dx = compute_age_from_dates( condition_info.name.dob, None, None, None, 'age', condition_info.diagnosed_date) return age_at_dx vals = { 'page': str(uuid4()), 'person': condition_info.name.name.id, 'age': patient_age_at_dx(), 'federation_account': condition_info.name.name.federation_account, 'page_type': 'medical', 'medical_context': 'health_condition', 'relevance': 'important', 'summary': condition_info.short_comment, 'info': condition_info.extra_info, 'author': condition_info.healthprof and condition_info.healthprof.name.rec_name, 'node': condition_info.institution and condition_info.institution.name.rec_name } if (condition_info.pathology): vals['health_condition'] = condition_info.pathology vals['health_condition_text'] = condition_info.pathology.rec_name vals['health_condition_code'] = condition_info.pathology.code pol.append(vals) Pol.create(pol) @classmethod def create(cls, vlist): # Execute first the creation of PoL health_condition_info = super(PatientDiseaseInfo, cls).create(vlist) cls.create_health_condition_pol(health_condition_info[0]) return health_condition_info # PATIENT APPOINTMENT class Appointment(ModelSQL, ModelView): 'Patient Appointments' __name__ = 'gnuhealth.appointment' name = fields.Char('Appointment ID', readonly=True) healthprof = fields.Many2One( 'gnuhealth.healthprofessional', 'Health Prof', select=True, help='Health Professional') patient = fields.Many2One( 'gnuhealth.patient', 'Patient', select=True, help='Patient Name', states={'required': (Eval('state') != 'free')}) appointment_date = fields.DateTime('Date and Time') checked_in_date = fields.DateTime('Checked-in Time') institution = fields.Many2One( 'gnuhealth.institution', 'Institution', help='Health Care Institution') speciality = fields.Many2One( 'gnuhealth.specialty', 'Specialty', help='Medical Specialty / Sector') state = fields.Selection([ (None, ''), ('free', 'Free'), ('confirmed', 'Confirmed'), ('checked_in', 'Checked in'), ('done', 'Done'), ('user_cancelled', 'Cancelled by patient'), ('center_cancelled', 'Cancelled by Health Center'), ('no_show', 'No show') ], 'State', sort=False) urgency = fields.Selection([ (None, ''), ('a', 'Normal'), ('b', 'Urgent'), ('c', 'Medical Emergency'), ], 'Urgency', sort=False) comments = fields.Text('Comments') appointment_type = fields.Selection([ (None, ''), ('outpatient', 'Outpatient'), ('inpatient', 'Inpatient'), ], 'Type', sort=False) visit_type = fields.Selection([ (None, ''), ('new', 'New health condition'), ('followup', 'Followup'), ('well_child', 'Well Child visit'), ('well_woman', 'Well Woman visit'), ('well_man', 'Well Man visit'), ], 'Visit', sort=False) consultations = fields.Many2One( 'product.product', 'Consultation Services', domain=[('type', '=', 'service')], help='Consultation Services') @classmethod def __setup__(cls): super(Appointment, cls).__setup__() cls._order.insert(0, ('appointment_date', 'DESC')) cls._buttons.update({ 'checked_in': {'invisible': Not(Equal(Eval('state'), 'confirmed'))} }) cls._buttons.update({ 'no_show': {'invisible': Not(Equal(Eval('state'), 'confirmed'))} }) @classmethod @ModelView.button def checked_in(cls, appointments): cls.write(appointments, { 'state': 'checked_in'}) @classmethod @ModelView.button def no_show(cls, appointments): cls.write(appointments, { 'state': 'no_show'}) @classmethod def search_rec_name(cls, name, clause): if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' return [bool_op, ('name',) + tuple(clause[1:]), ('patient',) + tuple(clause[1:]), ] @classmethod def generate_code(cls, **pattern): Config = Pool().get('gnuhealth.sequences') config = Config(1) sequence = config.get_multivalue( 'appointment_sequence', **pattern) if sequence: return sequence.get() @classmethod def create(cls, vlist): vlist = [x.copy() for x in vlist] for values in vlist: if values['state'] == 'confirmed' and not values.get('name'): values['name'] = cls.generate_code() return super(Appointment, cls).create(vlist) @classmethod def write(cls, appointments, values): for appointment in appointments: # Update the checked-in time only if unset if values.get('state') == 'checked_in' \ and values.get('checked_in_date') is None: values['checked_in_date'] = datetime.now() return super(Appointment, cls).write(appointments, values) @classmethod def copy(cls, appointments, default=None): if default is None: default = {} default = default.copy() default['name'] = None default['appointment_date'] = cls.default_appointment_date() default['state'] = cls.default_state() return super(Appointment, cls).copy(appointments, default=default) @staticmethod def default_healthprof(): return get_health_professional() @staticmethod def default_urgency(): return 'a' @staticmethod def default_appointment_date(): return datetime.now() @staticmethod def default_appointment_type(): return 'outpatient' @staticmethod def default_state(): return 'confirmed' @staticmethod def default_institution(): return get_institution() @fields.depends('patient') def on_change_patient(self): if self.patient: self.state = 'confirmed' @fields.depends('healthprof') def on_change_with_speciality(self): # Return the Current / Main speciality of the Health Professional # if this speciality has been specified in the HP record. if (self.healthprof and self.healthprof.main_specialty): specialty = self.healthprof.main_specialty.specialty.id return specialty @staticmethod def default_speciality(): # This method will assign the Main specialty to the appointment # if there is a health professional associated to the login user # as a default value. # It will be overwritten if the health professional is modified in # this view, the on_change_with will take effect. # Retrieve the health professional Main specialty, if assigned hp_party_id = get_health_professional() if hp_party_id: # Retrieve the health professional Main specialty, if assigned health_professional_obj = Pool().get( 'gnuhealth.healthprofessional') health_professional = health_professional_obj.search( [('id', '=', hp_party_id)], limit=1)[0] hp_main_specialty = health_professional.main_specialty if hp_main_specialty: return hp_main_specialty.specialty.id if hp_main_specialty: return hp_main_specialty.specialty.id def get_rec_name(self, name): return self.name class AppointmentReport(ModelSQL, ModelView): 'Appointment Report' __name__ = 'gnuhealth.appointment.report' ref = fields.Char('PUID') patient = fields.Many2One('gnuhealth.patient', 'Patient') healthprof = fields.Many2One('gnuhealth.healthprofessional', 'Health Prof') age = fields.Function(fields.Char('Age'), 'get_patient_age') gender = fields.Selection([ (None, ''), ('m', 'Male'), ('f', 'Female')], 'Gender') address = fields.Function(fields.Char('Address'), 'get_address') insurance = fields.Function(fields.Char('Insurance'), 'get_insurance') appointment_date = fields.Date('Date') appointment_date_time = fields.DateTime('Date and Time') diagnosis = fields.Function( fields.Many2One( 'gnuhealth.pathology', 'Main Codition'), 'get_diagnosis') @classmethod def __setup__(cls): super(AppointmentReport, cls).__setup__() cls._order.insert(0, ('appointment_date_time', 'ASC')) @classmethod def table_query(cls): pool = Pool() appointment = pool.get('gnuhealth.appointment').__table__() party = pool.get('party.party').__table__() patient = pool.get('gnuhealth.patient').__table__() join1 = Join(appointment, patient) join1.condition = join1.right.id == appointment.patient join2 = Join(join1, party) join2.condition = join2.right.id == join1.right.name where = Literal(True) if Transaction().context.get('date_start'): where &= (appointment.appointment_date >= Transaction().context['date_start']) if Transaction().context.get('date_end'): where &= (appointment.appointment_date < Transaction().context['date_end'] + timedelta(days=1)) if Transaction().context.get('healthprof'): where &= \ appointment.healthprof == Transaction().context['healthprof'] return join2.select( appointment.id, appointment.create_uid, appointment.create_date, appointment.write_uid, appointment.write_date, join2.right.ref, join1.right.id.as_('patient'), join2.right.gender, appointment.appointment_date, appointment.appointment_date.as_('appointment_date_time'), appointment.healthprof, where=where) def get_address(self, name): res = '' if self.patient.name.addresses: res = self.patient.name.addresses[0].full_address return res def get_insurance(self, name): res = '' if self.patient.current_insurance: res = self.patient.current_insurance.company.name return res def get_diagnosis(self, name): Evaluation = Pool().get('gnuhealth.patient.evaluation') res = None evaluations = Evaluation.search([ ('appointment', '=', self.id) ]) if evaluations: evaluation = evaluations[0] if evaluation.diagnosis: res = evaluation.diagnosis.id return res def get_patient_age(self, name): return self.patient.name.age class OpenAppointmentReportStart(ModelView): 'Open Appointment Report' __name__ = 'gnuhealth.appointment.report.open.start' date_start = fields.Date('Date Start', required=True) date_end = fields.Date('Date End', required=True) healthprof = fields.Many2One('gnuhealth.healthprofessional', 'Health Prof') @staticmethod def default_date_start(): return datetime.now() @staticmethod def default_date_end(): return datetime.now() @staticmethod def default_healthprof(): return get_health_professional() class OpenAppointmentReport(Wizard): 'Open Appointment Report' __name__ = 'gnuhealth.appointment.report.open' start = StateView( 'gnuhealth.appointment.report.open.start', 'health.appointments_report_open_start_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Open', 'open_', 'tryton-ok', default=True), ]) open_ = StateAction('health.act_appointments_report_view_tree') def do_open_(self, action): health_prof = '' if self.start.healthprof: health_prof = self.start.healthprof.id action['pyson_context'] = PYSONEncoder().encode({ 'date_start': self.start.date_start, 'date_end': self.start.date_end, 'healthprof': health_prof, }) # Show action name depending on whether is an specific hp # or for all professionals if health_prof: action['name'] += ' - %s' % (self.start.healthprof.rec_name) else: action['name'] += ' - %s' % "All Health Professionals" return action, {} def transition_open_(self): return 'end' # PATIENT MEDICATION TREATMENT class PatientMedication(ModelSQL, ModelView): 'Patient Medication' __name__ = 'gnuhealth.patient.medication' medicament = fields.Many2One( 'gnuhealth.medicament', 'Medicament', required=True, help='Prescribed Medicament') indication = fields.Many2One( 'gnuhealth.pathology', 'Indication', help='Choose a disease for this medicament from the disease list. It' ' can be an existing disease of the patient or a prophylactic.') name = fields.Many2One( 'gnuhealth.patient', 'Patient', readonly=True) healthprof = fields.Many2One( 'gnuhealth.healthprofessional', 'Health Prof', readonly=True, help='Health Professional who prescribed or reviewed the medicament') institution = fields.Many2One( 'gnuhealth.institution', 'Institution') is_active = fields.Boolean( 'Active', help='Check if the patient is currently taking the medication') discontinued = fields.Boolean( 'Discontinued') course_completed = fields.Boolean( 'Course Completed') discontinued_reason = fields.Char( 'Reason for discontinuation', states={ 'invisible': Not(Bool(Eval('discontinued'))), 'required': Bool(Eval('discontinued')), }, depends=['discontinued'], help='Short description for discontinuing the treatment',) adverse_reaction = fields.Text( 'Adverse Reactions', help='Side effects or adverse reactions that the patient experienced') notes = fields.Text('Extra Info') start_treatment = fields.DateTime( 'Start', help='Date of start of Treatment') end_treatment = fields.DateTime( 'End', help='Date of start of Treatment') dose = fields.Float( 'Dose', help='Amount of medication (eg, 250 mg) per dose') dose_unit = fields.Many2One( 'gnuhealth.dose.unit', 'dose unit', help='Unit of measure for the medication to be taken') route = fields.Many2One( 'gnuhealth.drug.route', 'Administration Route', help='Drug administration route code.') form = fields.Many2One( 'gnuhealth.drug.form', 'Form', help='Drug form, such as tablet or gel') qty = fields.Integer( 'x', help='Quantity of units (eg, 2 capsules) of the medicament') duration = fields.Integer( 'Treatment duration', help='Period that the patient must take the medication. in minutes,' ' hours, days, months, years or indefinately') duration_period = fields.Selection([ (None, ''), ('minutes', 'minutes'), ('hours', 'hours'), ('days', 'days'), ('months', 'months'), ('years', 'years'), ('indefinite', 'indefinite'), ], 'Treatment period', sort=False, help='Period that the patient must take the medication in minutes,' ' hours, days, months, years or indefinately') common_dosage = fields.Many2One( 'gnuhealth.medication.dosage', 'Frequency', help='Common / standard dosage frequency for this medicament') admin_times = fields.Char( 'Admin hours', help='Suggested administration hours. For example, at 08:00, 13:00' ' and 18:00 can be encoded like 08 13 18') frequency = fields.Integer( 'Frequency', help='Time in between doses the patient must wait (ie, for 1 pill' ' each 8 hours, put here 8 and select \"hours\" in the unit field') frequency_unit = fields.Selection([ (None, ''), ('seconds', 'seconds'), ('minutes', 'minutes'), ('hours', 'hours'), ('days', 'days'), ('weeks', 'weeks'), ('wr', 'when required'), ], 'unit', select=True, sort=False) frequency_prn = fields.Boolean( 'PRN', help='Use it as needed, pro re nata') infusion = fields.Boolean( 'Infusion', help='Mark if the medication is in the form of infusion' ' Intravenous, Gastrostomy tube, nasogastric, etc...') infusion_rate = fields.Float( 'Rate', states={'invisible': Not(Bool(Eval('infusion')))}) infusion_rate_units = fields.Selection([ (None, ''), ('mlhr', 'mL/hour'), ], 'Unit Rate', states={'invisible': Not(Bool(Eval('infusion')))}, select=True, sort=False) prescription = fields.Many2One( 'gnuhealth.prescription.order', 'Prescription', readonly=True, domain=[('patient', '=', Eval('name'))], depends=['name'], help='Related prescription') @classmethod def __setup__(cls): super(PatientMedication, cls).__setup__() cls._order.insert(0, ('is_active', 'DESC')) cls._order.insert(1, ('start_treatment', 'DESC')) @fields.depends('discontinued', 'course_completed') def on_change_with_is_active(self): return not (self.discontinued or self.course_completed) @fields.depends('is_active', 'course_completed') def on_change_with_discontinued(self): return not (self.is_active or self.course_completed) @fields.depends('is_active', 'discontinued') def on_change_with_course_completed(self): return not (self.is_active or self.discontinued) @staticmethod def default_is_active(): return True @staticmethod def default_frequency_unit(): return 'hours' @staticmethod def default_duration_period(): return 'days' @staticmethod def default_qty(): return 1 @staticmethod def default_institution(): return get_institution() @staticmethod def default_healthprof(): return get_health_professional() @classmethod def validate(cls, medications): super(PatientMedication, cls).validate(medications) for medication in medications: medication.validate_medication_dates() def validate_medication_dates(self): Lang = Pool().get('ir.lang') language, = Lang.search([ ('code', '=', Transaction().language), ]) if self.end_treatment: if (self.end_treatment < self.start_treatment): raise MedEndDateBeforeStart(gettext( 'health.msg_med_end_date_before_start', start_treatment=Lang.strftime( self.start_treatment, language.code, language.date), end_treatment=Lang.strftime( self.end_treatment, language.code, language.date), ) ) # PATIENT VACCINATION INFORMATION class PatientVaccination(ModelSQL, ModelView): 'Patient Vaccination information' __name__ = 'gnuhealth.vaccination' name = fields.Many2One('gnuhealth.patient', 'Patient', required=True) vaccine = fields.Many2One( 'gnuhealth.medicament', 'Vaccine', required=True, domain=[('is_vaccine', '=', True)], help='Vaccine Name. Make sure that the vaccine has all the' ' proper information at product level. Information such as provider,' ' supplier code, tracking number, etc.. This information must always' ' be present. If available, please copy / scan the vaccine leaflet' ' and attach it to this record') admin_route = fields.Selection([ (None, ''), ('im', 'Intramuscular'), ('sc', 'Subcutaneous'), ('id', 'Intradermal'), ('nas', 'Intranasal'), ('po', 'Oral'), ], 'Route', sort=False) picture = fields.Binary('Label') # Deprecated # Since 2.8, we include the expiration date and lot number information # in the health_stock module vaccine_expiration_date = fields.Date('Expiration date') vaccine_lot = fields.Char( 'Lot Number', help='Please check on the vaccine (product) production lot numberand' ' tracking number when available !') institution = fields.Many2One( 'gnuhealth.institution', 'Institution', help='Medical Center where the patient is being or was vaccinated') date = fields.DateTime('Date') dose = fields.Integer('Dose #') next_dose_date = fields.DateTime('Next Dose') observations = fields.Text('Observations') institution = fields.Many2One('gnuhealth.institution', 'Institution') healthprof = fields.Many2One( 'gnuhealth.healthprofessional', 'Health Prof', readonly=True, help="Health Professional who administered or reviewed the vaccine \ information") signed_by = fields.Many2One( 'gnuhealth.healthprofessional', 'Signed by', readonly=True, states={'invisible': Equal(Eval('state'), 'in_progress')}, help="Health Professional that signed the vaccination document") amount = fields.Float( 'Amount', help='Amount of vaccine administered, in mL . The dose per mL \ (eg, mcg, EL.U ..) can be found at the related medicament') admin_site = fields.Selection([ (None, ''), ('lvl', 'left vastus lateralis'), ('rvl', 'right vastus lateralis'), ('ld', 'left deltoid'), ('rd', 'right deltoid'), ('lalt', 'left anterolateral fat of thigh'), ('ralt', 'right anterolateral fat of thigh'), ('lpua', 'left posterolateral fat of upper arm'), ('rpua', 'right posterolateral fat of upper arm'), ('lfa', 'left fore arm'), ('rfa', 'right fore arm')], 'Admin Site') state = fields.Selection([ (None, ''), ('in_progress', 'In Progress'), ('done', 'Done'), ], 'State', readonly=True) @staticmethod def default_institution(): return get_institution() @staticmethod def default_healthprof(): return get_health_professional() @staticmethod def default_state(): return 'in_progress' @staticmethod def default_date(): return datetime.now() @staticmethod def default_dose(): return 1 @classmethod def __setup__(cls): super(PatientVaccination, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('dose_uniq', Unique(t, t.name, t.vaccine, t.dose), 'This vaccine dose has been given already to the patient'), ] cls._buttons.update({ 'sign': {'invisible': Equal(Eval('state'), 'done')} }) @classmethod @ModelView.button def sign(cls, vaccinations): # Change the state of the vaccination to "Done" # and write the name of the signing health professional signing_hp = get_health_professional() cls.write(vaccinations, { 'state': 'done', 'signed_by': signing_hp}) @classmethod def write(cls, vaccinations, vals): # Don't allow to modify the record if the vaccination has been signed if vaccinations[0].state == 'done': raise CanNotModifyVaccination( gettext('health.can_not_modify_vaccination') ) return super(PatientVaccination, cls).write(vaccinations, vals) @classmethod def validate(cls, vaccines): super(PatientVaccination, cls).validate(vaccines) for vaccine in vaccines: vaccine.validate_next_dose_date() def validate_next_dose_date(self): if (self.next_dose_date): if (self.next_dose_date < self.date): raise NextDoseBeforeFirst( gettext('health.msg_next_dose_before_first') ) class PatientPrescriptionOrder(ModelSQL, ModelView): 'Prescription Order' __name__ = 'gnuhealth.prescription.order' _rec_name = 'prescription_id' STATES = {'readonly': Not(Eval('state') == 'draft')} patient = fields.Many2One( 'gnuhealth.patient', 'Patient', required=True, states=STATES) prescription_id = fields.Char( 'Prescription ID', readonly=True, help='Type in the ID of this prescription') prescription_date = fields.DateTime('Prescription Date', states=STATES) user_id = fields.Many2One('res.user', 'Prescribing Doctor', readonly=True) pharmacy = fields.Many2One( 'party.party', 'Pharmacy', domain=[('is_pharmacy', '=', True)], states={ 'readonly': (Eval('state') != 'draft') & Bool(Eval('pharmacy')), }, depends=['state']) prescription_line = fields.One2Many( 'gnuhealth.prescription.line', 'name', 'Prescription line', states=STATES) notes = fields.Text('Prescription Notes', states=STATES) pregnancy_warning = fields.Boolean('Pregnancy Warning', readonly=True) prescription_warning_ack = fields.Boolean('Prescription verified', states=STATES) healthprof = fields.Many2One( 'gnuhealth.healthprofessional', 'Prescribed by', readonly=True) report_prescription_date = fields.Function(fields.Date( 'Prescription Date'), 'get_report_prescription_date') report_prescription_time = fields.Function(fields.Time( 'Prescription Time'), 'get_report_prescription_time') state = fields.Selection([ ('draft', 'Draft'), ('done', 'Done'), ('validated', 'Validated'), ], 'State', readonly=True, sort=False, states=STATES) @classmethod def __setup__(cls): super(PatientPrescriptionOrder, cls).__setup__() cls._order.insert(0, ('prescription_date', 'DESC')) cls._buttons.update({ 'create_prescription': {'invisible': Equal(Eval('state'), 'done')} }) @classmethod def validate(cls, prescriptions): super(PatientPrescriptionOrder, cls).validate(prescriptions) for prescription in prescriptions: prescription.check_health_professional() prescription.check_prescription_warning() def check_health_professional(self): if not self.healthprof: raise NoAssociatedHealthProfessional(gettext( ('health.msg_no_associated_health_professional')) ) def check_prescription_warning(self): if not self.prescription_warning_ack: raise DrugPregnancySafetyCheck(gettext( 'health.msg_drug_pregnancy_safety_check') ) @staticmethod def default_healthprof(): return get_health_professional() # Method that makes the doctor to acknowledge if there is any # warning in the prescription @fields.depends('patient') def on_change_patient(self): preg_warning = False presc_warning_ack = True if self.patient: # Trigger the warning if the patient is at a childbearing age if (self.patient.childbearing_age): preg_warning = True presc_warning_ack = False self.prescription_warning_ack = presc_warning_ack self.pregnancy_warning = preg_warning @staticmethod def default_prescription_date(): return datetime.now() @staticmethod def default_user_id(): User = Pool().get('res.user') user = User(Transaction().user) return int(user.id) @classmethod def default_state(cls): return 'draft' def get_report_prescription_date(self, name): Company = Pool().get('company.company') timezone = None company_id = Transaction().context.get('company') if company_id: company = Company(company_id) if company.timezone: timezone = pytz.timezone(company.timezone) dt = self.prescription_date return datetime.astimezone(dt.replace(tzinfo=pytz.utc), timezone).date() def get_report_prescription_time(self, name): Company = Pool().get('company.company') timezone = None company_id = Transaction().context.get('company') if company_id: company = Company(company_id) if company.timezone: timezone = pytz.timezone(company.timezone) dt = self.prescription_date return datetime.astimezone(dt.replace(tzinfo=pytz.utc), timezone).time() @classmethod def create_prescription_pol(cls, prescription_info): """ Adds an entry in the person Page of Life related to this person prescription """ Pol = Pool().get('gnuhealth.pol') pol = [] plines = "" for line in prescription_info.prescription_line: plines = plines + line.rec_name + "\n" vals = { 'page': str(uuid4()), 'person': prescription_info.patient.name.id, 'page_date': prescription_info.prescription_date, 'federation_account': prescription_info.patient.name.federation_account, 'page_type': 'medical', 'medical_context': 'prescription', 'relevance': 'important', 'info': plines, 'author': prescription_info.healthprof and prescription_info.healthprof.name.rec_name, } pol.append(vals) Pol.create(pol) @classmethod def generate_code(cls, **pattern): Config = Pool().get('gnuhealth.sequences') config = Config(1) sequence = config.get_multivalue( 'prescription_sequence', **pattern) if sequence: return sequence.get() @classmethod def create(cls, vlist): vlist = [x.copy() for x in vlist] for values in vlist: if not values.get('prescription_id'): values['prescription_id'] = cls.generate_code() return super(PatientPrescriptionOrder, cls).create(vlist) @classmethod def copy(cls, prescriptions, default=None): if default is None: default = {} default = default.copy() default['prescription_id'] = None default['prescription_date'] = cls.default_prescription_date() return super(PatientPrescriptionOrder, cls).copy( prescriptions, default=default) @classmethod @ModelView.button def create_prescription(cls, prescriptions): prescription = prescriptions[0] # Change the state of the prescription to "Done" cls.write(prescriptions, { 'state': 'done', }) # Create prescription PoL if the person has a federation account. if (prescription.patient.name.federation_account): cls.create_prescription_pol(prescription) @classmethod def search_rec_name(cls, name, clause): if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' return [bool_op, ('patient',) + tuple(clause[1:]), ('prescription_id',) + tuple(clause[1:]), ] # PRESCRIPTION LINE class PrescriptionLine(ModelSQL, ModelView): 'Prescription Line' __name__ = 'gnuhealth.prescription.line' # Remove inherits to be compatible with Tryton 2.8 # _inherits = {'gnuhealth.medication.template': 'template'} # template = fields.Many2One('gnuhealth.medication.template', # 'Medication Template') name = fields.Many2One('gnuhealth.prescription.order', 'Prescription ID') review = fields.DateTime('Valid Until', help="Until this date, the patient" "usually can ask for a refill / " "reorder of this medicament") quantity = fields.Integer( 'Units', help="Number of units of the medicament." " Example : 30 capsules of amoxicillin") refills = fields.Integer('Refills #') allow_substitution = fields.Boolean('Allow substitution') short_comment = fields.Char( 'Comment', help='Short comment on the specific drug') prnt = fields.Boolean( 'Print', help='Check this box to print this line of the prescription.') add_to_history = fields.Boolean( 'Hist', help='Include this medication in the patient medication history') medicament = fields.Many2One( 'gnuhealth.medicament', 'Medicament', required=True, help='Prescribed Medicament') indication = fields.Many2One( 'gnuhealth.pathology', 'Indication', help='Choose a disease for this medicament from the disease list. It' ' can be an existing disease of the patient or a prophylactic.') start_treatment = fields.DateTime( 'Start', help='Date of start of Treatment') end_treatment = fields.DateTime( 'End', help='Date of start of Treatment') dose = fields.Float( 'Dose', help='Amount of medication (eg, 250 mg) per dose') dose_unit = fields.Many2One( 'gnuhealth.dose.unit', 'dose unit', help='Unit of measure for the medication to be taken') route = fields.Many2One( 'gnuhealth.drug.route', 'Administration Route', help='Drug administration route code.') form = fields.Many2One( 'gnuhealth.drug.form', 'Form', help='Drug form, such as tablet or gel') qty = fields.Integer( 'x', help='Quantity of units (eg, 2 capsules) of the medicament') common_dosage = fields.Many2One( 'gnuhealth.medication.dosage', 'Frequency', help='Common / standard dosage frequency for this medicament') admin_times = fields.Char( 'Admin hours', help='Suggested administration hours. For example, at 08:00, 13:00' ' and 18:00 can be encoded like 08 13 18') frequency = fields.Integer( 'Frequency', help='Time in between doses the patient must wait (ie, for 1 pill' ' each 8 hours, put here 8 and select \"hours\" in the unit field') frequency_unit = fields.Selection([ (None, ''), ('seconds', 'seconds'), ('minutes', 'minutes'), ('hours', 'hours'), ('days', 'days'), ('weeks', 'weeks'), ('wr', 'when required'), ], 'unit', select=True, sort=False) frequency_prn = fields.Boolean('PRN', help='Use it as needed, pro re nata') duration = fields.Integer( 'Treatment duration', help='Period that the patient must take the medication. in minutes,' ' hours, days, months, years or indefinately') duration_period = fields.Selection([ (None, ''), ('minutes', 'minutes'), ('hours', 'hours'), ('days', 'days'), ('months', 'months'), ('years', 'years'), ('indefinite', 'indefinite'), ], 'Treatment period', sort=False, help='Period that the patient must take the medication in minutes,' ' hours, days, months, years or indefinately') infusion = fields.Boolean( 'Infusion', help='Mark if the medication is in the form of infusion' ' Intravenous, Gastrostomy tube, nasogastric, etc...') infusion_rate = fields.Float( 'Rate', states={'invisible': Not(Bool(Eval('infusion')))}) infusion_rate_units = fields.Selection([ (None, ''), ('mlhr', 'mL/hour'), ], 'Unit Rate', states={'invisible': Not(Bool(Eval('infusion')))}, select=True, sort=False) def get_rec_name(self, name): dose = "" if (self.dose): dose_unit = "" dose = str(self.dose) if self.dose_unit: dose_unit = self.dose_unit.rec_name dose = dose + dose_unit return str(self.medicament.rec_name + " " + dose) @staticmethod def default_qty(): return 1 @staticmethod def default_duration_period(): return 'days' @staticmethod def default_frequency_unit(): return 'hours' @staticmethod def default_quantity(): return 1 @staticmethod def default_prnt(): return True @staticmethod def default_start_treatment(): return datetime.now() @fields.depends('medicament') def on_change_medicament(self): if self.medicament: self.dose = self.medicament.strength self.dose_unit = self.medicament.unit self.form = self.medicament.form self.route = self.medicament.route @classmethod def create(cls, vlist): vlist = [x.copy() for x in vlist] for values in vlist: if values.get('add_to_history'): Medication = Pool().get('gnuhealth.patient.medication') med = [] # Retrieve the patient ID from the prescription Prescs = Pool().get('gnuhealth.prescription.order') patient = Prescs.browse([values['name']])[0].patient.id medicament = values['medicament'] indication = values['indication'] start_treatment = values.get('start_treatment') prescription = values['name'] values = { 'name': patient, 'medicament': medicament, 'indication': indication, 'start_treatment': start_treatment, 'prescription': prescription } # Add the medicament from the prescription med.append(values) Medication.create(med) return super(PrescriptionLine, cls).create(vlist) class PatientEvaluation(ModelSQL, ModelView, MultiValueMixin): 'Patient Evaluation' __name__ = 'gnuhealth.patient.evaluation' STATES = {'readonly': Eval('state') == 'signed'} def patient_age_at_evaluation(self, name): if (self.patient.name.dob and self.evaluation_start): return compute_age_from_dates( self.patient.name.dob, None, None, None, 'age', self.evaluation_start.date()) def evaluation_duration(self, name): if (self.evaluation_endtime and self.evaluation_start): return self.evaluation_endtime - self.evaluation_start def get_wait_time(self, name): # Compute wait time between checked-in and start of evaluation if self.appointment: if self.appointment.checked_in_date: if self.appointment.checked_in_date < self.evaluation_start: return self.evaluation_start - \ self.appointment.checked_in_date code = fields.Char('Code', help="Unique code that \ identifies the evaluation") patient = fields.Many2One('gnuhealth.patient', 'Patient', states=STATES) appointment = fields.Many2One( 'gnuhealth.appointment', 'Appointment', domain=[('patient', '=', Eval('patient'))], depends=['patient'], help='Enter or select the date / ID of the appointment related to' ' this evaluation', states=STATES) related_condition = fields.Many2One( 'gnuhealth.patient.disease', 'Related condition', domain=[('name', '=', Eval('patient'))], depends=['patient'], help="Related condition related to this follow-up evaluation", states={'invisible': (Eval('visit_type') != 'followup')}) evaluation_start = fields.DateTime('Start', required=True, states=STATES) evaluation_endtime = fields.DateTime('End', states=STATES) evaluation_length = fields.Function( fields.TimeDelta( 'Evaluation length', help="Duration of the evaluation"), 'evaluation_duration') wait_time = fields.Function( fields.TimeDelta('Patient wait time', help="How long the patient waited"), 'get_wait_time') state = fields.Selection([ (None, ''), ('in_progress', 'In progress'), ('done', 'Done'), ('signed', 'Signed'), ], 'State', readonly=True, sort=False) next_evaluation = fields.Many2One( 'gnuhealth.appointment', 'Next Appointment', domain=[('patient', '=', Eval('patient'))], depends=['patient'], states=STATES) user_id = fields.Many2One('res.user', 'Last Changed by', readonly=True) healthprof = fields.Many2One( 'gnuhealth.healthprofessional', 'Health Prof', help="Health professional that initiates the evaluation." "This health professional might or might not be the same that" " signs and finishes the evaluation." "The evaluation remains in progress state until it is signed" ", when it becomes read-only", readonly=True) signed_by = fields.Many2One( 'gnuhealth.healthprofessional', 'Health Prof', readonly=True, states={'invisible': Equal(Eval('state'), 'in_progress')}, help="Health Professional that finished the patient evaluation") specialty = fields.Many2One( 'gnuhealth.specialty', 'Specialty', states=STATES) visit_type = fields.Selection([ (None, ''), ('new', 'New health condition'), ('followup', 'Followup'), ('well_child', 'Well Child visit'), ('well_woman', 'Well Woman visit'), ('well_man', 'Well Man visit'), ], 'Visit', sort=False, states=STATES) urgency = fields.Selection([ (None, ''), ('a', 'Normal'), ('b', 'Urgent'), ('c', 'Medical Emergency'), ], 'Urgency', sort=False, states=STATES) computed_age = fields.Function(fields.Char( 'Age', help="Computed patient age at the moment of the evaluation"), 'patient_age_at_evaluation') gender = fields.Function(fields.Selection([ (None, ''), ('m', 'Male'), ('f', 'Female'), ('f-m', 'Female -> Male'), ('m-f', 'Male -> Female'), ], 'Gender'), 'get_patient_gender', searcher='search_patient_gender') information_source = fields.Char( 'Source', help="Source of" "Information, eg : Self, relative, friend ...", states=STATES) reliable_info = fields.Boolean( 'Reliable', help="Uncheck this option" "if the information provided by the source seems not reliable", states=STATES) derived_from = fields.Many2One( 'gnuhealth.healthprofessional', 'Derived from', help='Health Professional who derived the case', states=STATES) derived_to = fields.Many2One( 'gnuhealth.healthprofessional', 'Derived to', help='Health Professional to derive the case', states=STATES) evaluation_type = fields.Selection([ (None, ''), ('outpatient', 'Outpatient'), ('inpatient', 'Inpatient'), ], 'Type', sort=False, states=STATES) chief_complaint = fields.Char('Chief Complaint', help='Chief Complaint', states=STATES) notes_complaint = fields.Text('Complaint details', states=STATES) present_illness = fields.Text('Present Illness', states=STATES) evaluation_summary = fields.Text('Clinical and physical', states=STATES) glycemia = fields.Float( 'Glycemia', help='Blood glucose level (mg/dL)', states=STATES) hba1c = fields.Float( 'HbA1c', help='Last Glycated Hemoglobin HbA1c level(mmol/mol)', states=STATES) cholesterol_total = fields.Integer( 'Last Cholesterol', help='Last cholesterol reading (mg/dL)', states=STATES) hdl = fields.Integer( 'HDL', help='Last HDL Cholesterol reading (mg/dL)', states=STATES) ldl = fields.Integer( 'LDL', help='Last LDL Cholesterol reading (mg/dL)', states=STATES) tag = fields.Integer( 'TAGs', help='Last Triglicerides level, (mg/dL)', states=STATES) systolic = fields.Integer( 'Systolic Pressure', help='Systolic pressure (mmHg)', states=STATES) diastolic = fields.Integer( 'Diastolic Pressure', help='Diastolic pressure (mmHg)', states=STATES) bpm = fields.Integer( 'Heart Rate', help='Heart rate (beats per minute)', states=STATES) respiratory_rate = fields.Integer( 'Respiratory Rate', help='Respiratory rate expressed in breaths per minute', states=STATES) osat = fields.Integer( 'Oxygen Saturation', help='Arterial oxygen saturation expressed as a percentage', states=STATES) malnutrition = fields.Boolean( 'Malnutrition', help='Check this box if the patient show signs of malnutrition. If' ' associated to a disease, please encode the correspondent disease' ' on the patient disease history. For example, Moderate' ' protein-energy malnutrition, E44.0 in ICD-10 encoding', states=STATES) dehydration = fields.Boolean( 'Dehydration', help='Check this box if the patient show signs of dehydration. If' ' associated to a disease, please encode the correspondent disease' ' on the patient disease history. For example, Volume Depletion, E86' ' in ICD-10 encoding', states=STATES) temperature = fields.Float( 'Temperature', help='Temperature in celcius', states=STATES) weight = fields.Float('Weight', digits=(3, 2), help='Weight in kilos', states=STATES) height = fields.Float('Height', digits=(3, 1), help='Height in centimeters', states=STATES) bmi = fields.Float( 'BMI', digits=(2, 2), help='Body mass index', states=STATES) head_circumference = fields.Float( 'Head', help='Head circumference in centimeters', states=STATES) abdominal_circ = fields.Float('Waist', digits=(3, 1), help='Waist circumference in centimeters', states=STATES) hip = fields.Float('Hip', digits=(3, 1), help='Hip circumference in centimeters', states=STATES) whr = fields.Float( 'WHR', digits=(2, 2), help='Waist to hip ratio . Reference values:\n' 'Men : < 0.9 Normal // 0.9 - 0.99 Overweight // > 1 Obesity \n' 'Women : < 0.8 Normal // 0.8 - 0.84 Overweight // > 0.85 Obesity', states=STATES) # DEPRECATION NOTE : SIGNS AND SYMPTOMS FIELDS TO BE REMOVED IN 1.6 . # NOW WE USE A O2M OBJECT TO MAKE IT MORE SCALABLE, CLEARER AND FUNCTIONAL # TO WORK WITH THE CLINICAL FINDINGS OF THE PATIENT loc = fields.Integer( 'Glasgow', help='Level of Consciousness - on Glasgow Coma Scale : < 9 severe -' ' 9-12 Moderate, > 13 minor', states=STATES) loc_eyes = fields.Selection([ ('1', 'Does not Open Eyes'), ('2', 'Opens eyes in response to painful stimuli'), ('3', 'Opens eyes in response to voice'), ('4', 'Opens eyes spontaneously'), ], 'Glasgow - Eyes', sort=False, states=STATES) loc_verbal = fields.Selection([ ('1', 'Makes no sounds'), ('2', 'Incomprehensible sounds'), ('3', 'Utters inappropriate words'), ('4', 'Confused, disoriented'), ('5', 'Oriented, converses normally'), ], 'Glasgow - Verbal', sort=False, states=STATES) loc_motor = fields.Selection([ ('1', 'Makes no movement'), ('2', 'Extension to painful stimuli - decerebrate response -'), ('3', 'Abnormal flexion to painful stimuli (decorticate response)'), ('4', 'Flexion / Withdrawal to painful stimuli'), ('5', 'Localizes painful stimuli'), ('6', 'Obeys commands'), ], 'Glasgow - Motor', sort=False, states=STATES) tremor = fields.Boolean( 'Tremor', help='If associated to a disease, please encode it on the patient' ' disease history', states=STATES) violent = fields.Boolean( 'Violent Behaviour', help='Check this box if the patient is aggressive or violent at the' ' moment', states=STATES) mood = fields.Selection([ (None, ''), ('n', 'Normal'), ('s', 'Sad'), ('f', 'Fear'), ('r', 'Rage'), ('h', 'Happy'), ('d', 'Disgust'), ('e', 'Euphoria'), ('fl', 'Flat'), ], 'Mood', sort=False, states=STATES) orientation = fields.Boolean( 'Orientation', help='Check this box if the patient is disoriented in time and/or' ' space', states=STATES) memory = fields.Boolean( 'Memory', help='Check this box if the patient has problems in short or long' ' term memory', states=STATES) knowledge_current_events = fields.Boolean( 'Knowledge of Current Events', help='Check this box if the patient can not respond to public' ' notorious events', states=STATES) judgment = fields.Boolean( 'Judgment', help='Check this box if the patient can not interpret basic scenario' ' solutions', states=STATES) abstraction = fields.Boolean( 'Abstraction', help='Check this box if the patient presents abnormalities in' ' abstract reasoning', states=STATES) vocabulary = fields.Boolean( 'Vocabulary', help='Check this box if the patient lacks basic intelectual capacity,' ' when she/he can not describe elementary objects', states=STATES) calculation_ability = fields.Boolean( 'Calculation Ability', help='Check this box if the patient can not do simple arithmetic' ' problems', states=STATES) object_recognition = fields.Boolean( 'Object Recognition', help='Check this box if the patient suffers from any sort of gnosia' ' disorders, such as agnosia, prosopagnosia ...', states=STATES) praxis = fields.Boolean( 'Praxis', help='Check this box if the patient is unable to make voluntary' 'movements', states=STATES) diagnosis = fields.Many2One( 'gnuhealth.pathology', 'Main Condition', help='Presumptive Diagnosis. If no diagnosis can be made' ', encode the main sign or symptom.', states=STATES) secondary_conditions = fields.One2Many( 'gnuhealth.secondary_condition', 'evaluation', 'Other Conditions', help='Other ' ' conditions found on the patient in the context' ' of this evaluation', states=STATES) diagnostic_hypothesis = fields.One2Many( 'gnuhealth.diagnostic_hypothesis', 'evaluation', 'Hypotheses / DDx', help='Other Diagnostic Hypotheses /' ' Differential Diagnosis (DDx)', states=STATES) signs_and_symptoms = fields.One2Many( 'gnuhealth.signs_and_symptoms', 'evaluation', 'Signs and Symptoms', help='Enter the Signs and Symptoms' ' for the patient in this evaluation.', states=STATES) psychological_assessment = fields.Text("Psychological Assessment", states=STATES) info_diagnosis = fields.Text('Presumptive Diagnosis: Extra Info', states=STATES) directions = fields.Text('Plan', states=STATES) actions = fields.One2Many( 'gnuhealth.directions', 'name', 'Procedures', help='Procedures / Actions to take', states=STATES) notes = fields.Text('Notes', states=STATES) discharge_reason = fields.Selection([ (None, ''), ('home', 'Home / Selfcare'), ('transfer', 'Transferred to another institution'), ('against_advice', 'Left against medical advice'), ('death', 'Death')], 'Discharge Reason', required=True, sort=False, states={'invisible': Equal(Eval('state'), 'in_progress'), 'readonly': Eval('state') == 'signed'}, help="Reason for patient discharge") institution = fields.Many2One('gnuhealth.institution', 'Institution', states=STATES) report_evaluation_date = fields.Function(fields.Date( 'Evaluation Date'), 'get_report_evaluation_date') report_evaluation_time = fields.Function(fields.Time( 'Evaluation Time'), 'get_report_evaluation_time') @staticmethod def default_institution(): # health_inst = HealthInstitution() return get_institution() @staticmethod def default_discharge_reason(): return 'home' def get_patient_gender(self, name): return self.patient.gender @classmethod def search_patient_gender(cls, name, clause): res = [] value = clause[2] res.append(('patient.name.gender', clause[1], value)) return res @classmethod def validate(cls, evaluations): super(PatientEvaluation, cls).validate(evaluations) for evaluation in evaluations: evaluation.validate_evaluation_period() evaluation.check_health_professional() def validate_evaluation_period(self): Lang = Pool().get('ir.lang') language, = Lang.search([ ('code', '=', Transaction().language), ]) if (self.evaluation_endtime and self.evaluation_start): if (self.evaluation_endtime < self.evaluation_start): raise EvaluationEndBeforeStart(gettext( 'health.msg_end_evaluation_time_before_start', evaluation_start=Lang.strftime( self.evaluation_start, language.code, language.date), evaluation_endtime=Lang.strftime( self.evaluation_endtime, language.code, language.date), ) ) def check_health_professional(self): if not self.healthprof: raise NoAssociatedHealthProfessional(gettext( ('health.msg_no_associated_health_professional')) ) @staticmethod def default_healthprof(): return get_health_professional() @staticmethod def default_loc_eyes(): return '4' @staticmethod def default_loc_verbal(): return '5' @staticmethod def default_loc_motor(): return '6' @staticmethod def default_loc(): return 15 @staticmethod def default_evaluation_type(): return 'outpatient' @staticmethod def default_state(): return 'in_progress' @fields.depends('weight', 'height') def on_change_with_bmi(self): if self.height and self.weight: if (self.height > 0): return round(self.weight / ((self.height / 100) ** 2), 2) return 0 @fields.depends('weight', 'height', 'bmi') def on_change_bmi(self): if self.height and self.weight: if (self.height > 0): self.bmi = round(self.weight / ((self.height / 100) ** 2), 2) elif (self.height and not self.weight): self.weight = round((((self.height / 100) ** 2) * self.bmi), 2) elif (self.weight and not self.height): self.height = round(((self.weight / self.bmi)**(0.5)*100), 2) @fields.depends('loc_verbal', 'loc_motor', 'loc_eyes') def on_change_with_loc(self): return int(self.loc_motor) + int(self.loc_eyes) + int(self.loc_verbal) # Show the gender and age upon entering the patient # These two are function fields (don't exist at DB level) @fields.depends('patient') def on_change_patient(self): self.computed_age = self.patient.age @staticmethod def default_information_source(): return 'Self' @staticmethod def default_reliable_info(): return True @staticmethod def default_evaluation_start(): return datetime.now() # Calculate the WH ratio @fields.depends('abdominal_circ', 'hip', 'whr') def on_change_with_whr(self): waist = self.abdominal_circ hip = self.hip if waist and hip: if (hip > 0): whr = round((waist / hip), 2) else: whr = 0 return whr def get_rec_name(self, name): return str(self.evaluation_start) def get_report_evaluation_date(self, name): Company = Pool().get('company.company') timezone = None company_id = Transaction().context.get('company') if company_id: company = Company(company_id) if company.timezone: timezone = pytz.timezone(company.timezone) dt = self.evaluation_start return datetime.astimezone( dt.replace(tzinfo=pytz.utc), timezone).date() def get_report_evaluation_time(self, name): Company = Pool().get('company.company') timezone = None company_id = Transaction().context.get('company') if company_id: company = Company(company_id) if company.timezone: timezone = pytz.timezone(company.timezone) dt = self.evaluation_start return datetime.astimezone( dt.replace(tzinfo=pytz.utc), timezone).time() @classmethod def __setup__(cls): super(PatientEvaluation, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('code_unique', Unique(t, t.code), 'The evaluation code must be unique !'), ] cls._order.insert(0, ('evaluation_start', 'DESC')) cls._buttons.update({ 'end_evaluation': {'invisible': Or(Equal(Eval('state'), 'signed'), Equal(Eval('state'), 'done'))} }) @classmethod def generate_code(cls, **pattern): Config = Pool().get('gnuhealth.sequences') config = Config(1) sequence = config.get_multivalue( 'patient_evaluation_sequence', **pattern) if sequence: return sequence.get() @classmethod def create(cls, vlist): vlist = [x.copy() for x in vlist] for values in vlist: if not values.get('code'): values['code'] = cls.generate_code() return super(PatientEvaluation, cls).create(vlist) # End the evaluation and discharge the patient @classmethod @ModelView.button def end_evaluation(cls, evaluations): pool = Pool() Appointment = pool.get('gnuhealth.appointment') evaluation_id = evaluations[0] patient_app = [] # Change the state of the evaluation to "Done" signing_hp = get_health_professional() cls.write(evaluations, { 'state': 'done', 'signed_by': signing_hp, 'evaluation_endtime': datetime.now() }) # If there is an appointment associated to this evaluation # set it to state "Done" if evaluations[0].appointment: patient_app.append(evaluations[0].appointment) Appointment.write(patient_app, { 'state': 'done', }) # Create an entry in the page of life # It will create the entry at the moment of # discharging the patient # The patient needs to have a federation account if (evaluation_id.patient.name.federation_account): cls.create_evaluation_pol(evaluation_id) @classmethod def create_evaluation_pol(cls, evaluation): """ Adds an entry in the person Page of Life related to this medical evaluation. """ Pol = Pool().get('gnuhealth.pol') pol = [] # Create a dictionary with vital signs and other measurements measurements = {} bp = {} if evaluation.systolic: bp['systolic'] = evaluation.systolic if evaluation.diastolic: bp['diastolic'] = evaluation.diastolic if bp: measurements['bp'] = bp if evaluation.temperature: measurements['t'] = evaluation.temperature if evaluation.bpm: measurements['hr'] = evaluation.bpm if evaluation.respiratory_rate: measurements['rr'] = evaluation.respiratory_rate if evaluation.osat: measurements['osat'] = evaluation.osat if evaluation.glycemia: measurements['bg'] = evaluation.glycemia if evaluation.weight: measurements['wt'] = evaluation.weight if evaluation.height: measurements['ht'] = evaluation.height if evaluation.bmi: measurements['bmi'] = evaluation.bmi if evaluation.head_circumference: measurements['hc'] = evaluation.head_circumference assessment = (evaluation.diagnosis and evaluation.diagnosis.rec_name) or '' measures = str(measurements) # Summarize the encounter note taking as SOAP soap = \ f"S: {evaluation.chief_complaint}\n--\n" f"{evaluation.present_illness}\n" f"O: {evaluation.evaluation_summary}\n" f" {measures}\n" f"A: {assessment}\n" f"P: {evaluation.directions}" vals = { 'page': str(uuid4()), 'person': evaluation.patient.name.id, 'page_date': evaluation.evaluation_start, 'age': evaluation.computed_age, 'federation_account': evaluation.patient.name.federation_account, 'page_type': 'medical', 'medical_context': 'encounter', 'relevance': 'important', 'summary': evaluation.chief_complaint, 'info': soap, 'measurements': measures, 'author': evaluation.healthprof.name.rec_name, 'author_acct': evaluation.healthprof.name.federation_account, 'node': evaluation.institution.name.name, } if (evaluation.diagnosis): vals['health_condition_text'] = evaluation.diagnosis.rec_name vals['health_condition_code'] = evaluation.diagnosis.code pol.append(vals) Pol.create(pol) # Search by the health condition code or the description @classmethod def search_rec_name(cls, name, clause): if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' return [bool_op, ('patient',) + tuple(clause[1:]), ('code',) + tuple(clause[1:]), ] # PATIENT EVALUATION DIRECTIONS class Directions(ModelSQL, ModelView): 'Patient Directions' __name__ = 'gnuhealth.directions' name = fields.Many2One( 'gnuhealth.patient.evaluation', 'Evaluation', readonly=True) procedure = fields.Many2One( 'gnuhealth.procedure', 'Procedure', required=True) comments = fields.Char('Comments') # SECONDARY CONDITIONS ASSOCIATED TO THE PATIENT IN THE EVALUATION class SecondaryCondition(ModelSQL, ModelView): 'Secondary Conditions' __name__ = 'gnuhealth.secondary_condition' evaluation = fields.Many2One( 'gnuhealth.patient.evaluation', 'Evaluation', readonly=True) pathology = fields.Many2One( 'gnuhealth.pathology', 'Pathology', required=True) comments = fields.Char('Comments') # PATIENT EVALUATION OTHER DIAGNOSTIC HYPOTHESES class DiagnosticHypothesis(ModelSQL, ModelView): 'Other Diagnostic Hypothesis' __name__ = 'gnuhealth.diagnostic_hypothesis' evaluation = fields.Many2One( 'gnuhealth.patient.evaluation', 'Evaluation', readonly=True) pathology = fields.Many2One( 'gnuhealth.pathology', 'Pathology', required=True) comments = fields.Char('Comments') # PATIENT EVALUATION CLINICAL FINDINGS (SIGNS AND SYMPTOMS) class SignsAndSymptoms(ModelSQL, ModelView): 'Evaluation Signs and Symptoms' __name__ = 'gnuhealth.signs_and_symptoms' evaluation = fields.Many2One( 'gnuhealth.patient.evaluation', 'Evaluation', readonly=True) sign_or_symptom = fields.Selection([ (None, ''), ('sign', 'Sign'), ('symptom', 'Symptom')], 'Subjective / Objective', required=True) clinical = fields.Many2One( 'gnuhealth.pathology', 'Sign or Symptom', required=True) comments = fields.Char('Comments') # ECG class PatientECG(ModelSQL, ModelView): 'Patient ECG' __name__ = 'gnuhealth.patient.ecg' name = fields.Many2One('gnuhealth.patient', 'Patient', required=True) ecg_date = fields.DateTime('Date', required=True) lead = fields.Selection([ (None, ''), ('i', 'I'), ('ii', 'II'), ('iii', 'III'), ('avf', 'aVF'), ('avr', 'aVR'), ('avl', 'aVL'), ('v1', 'V1'), ('v2', 'V2'), ('v3', 'V3'), ('v4', 'V4'), ('v5', 'V5'), ('v6', 'V6')], 'Lead', sort=False) axis = fields.Selection([ (None, ''), ('normal', 'Normal Axis'), ('left', 'Left deviation'), ('right', 'Right deviation'), ('extreme_right', 'Extreme right deviation')], 'Axis', sort=False, required=True) rate = fields.Integer('Rate', required=True) rhythm = fields.Selection([ (None, ''), ('regular', 'Regular'), ('irregular', 'Irregular')], 'Rhythm', sort=False, required=True) pacemaker = fields.Selection([ (None, ''), ('sa', 'Sinus Node'), ('av', 'Atrioventricular'), ('pk', 'Purkinje') ], 'Pacemaker', sort=False, required=True) pr = fields.Integer('PR', help="Duration of PR interval in milliseconds") qrs = fields.Integer('QRS', help="Duration of QRS interval in milliseconds") qt = fields.Integer('QT', help="Duration of QT interval in milliseconds") st_segment = fields.Selection([ (None, ''), ('normal', 'Normal'), ('depressed', 'Depressed'), ('elevated', 'Elevated')], 'ST Segment', sort=False, required=True) twave_inversion = fields.Boolean('T wave inversion') interpretation = fields.Char('Interpretation', required=True) ecg_strip = fields.Binary('ECG Strip') healthprof = fields.Many2One( 'gnuhealth.healthprofessional', 'Health Prof', readonly=True, help='Health Professional who performed the ECG') institution = fields.Many2One('gnuhealth.institution', 'Institution') # Default ECG date @staticmethod def default_ecg_date(): return datetime.now() @staticmethod def default_institution(): return get_institution() @staticmethod def default_healthprof(): return get_health_professional() @classmethod def validate(cls, ecgs): super(PatientECG, cls).validate(ecgs) for ecg in ecgs: ecg.check_health_professional() def check_health_professional(self): if not self.healthprof: raise NoAssociatedHealthProfessional(gettext( ('health.msg_no_associated_health_professional')) ) # Return the ECG Interpretation with main components def get_rec_name(self, name): if self.name: res = str(self.interpretation) + ' // Rate ' + str(self.rate) return res @classmethod def __setup__(cls): super(PatientECG, cls).__setup__() cls._order.insert(0, ('ecg_date', 'DESC')) @classmethod def search_rec_name(cls, name, clause): if clause[1].startswith('!') or clause[1].startswith('not '): bool_op = 'AND' else: bool_op = 'OR' return [bool_op, ('name',) + tuple(clause[1:]), ] class ProductTemplate(ModelSQL, ModelView): __name__ = 'product.template' """ Allow to change the values from the product templates coming from XML files """ @classmethod def check_xml_record(cls, records, values): return True class Commands(ModelSQL, ModelView): 'GNU Health Commands' __name__ = "gnuhealth.command" name = fields.Char('Command') label = fields.Char('Label') internal = fields.Boolean('Internal', help="Internal, Kernel commands") model = fields.Char('Model') domain = fields.Char('Domain') description = fields.Char("Description") @staticmethod def sysinfo(): # Get Server side information info = '' os_header = "\n-- Operating System / Distribution --\n" uname = platform.uname() pversion = f"Python version: {str(platform.python_version())}\n" # Get OS version and related info. os_info = '' with open('/etc/os-release') as f: os_release = f.readlines() for line in os_release: os_info = os_info + str(line) # Get relevant environment variables gnuhealth_os_user = f"GNU Health user: {os.environ['USER']}\n" gnuhealth_version = f"GNU Health Server version:" \ f"{os.environ['GNUHEALTH_VERSION']}\n" tryton_version = f"Tryton server: {os.environ['TRYTON_VERSION']}\n" info = f"{info} {gnuhealth_version} {tryton_version}" \ f"{gnuhealth_os_user}\n{pversion}\n" \ f"{os_header}\n{os_info}\n" \ f"Platform / Kernel Info: {str(uname)}\n" return info @classmethod def __setup__(cls): super(Commands, cls).__setup__() cls.__rpc__.update({ 'sysinfo': RPC(check_access=False), }) class Modules(ModelSQL, ModelView): __name__ = 'ir.module' # Add the module description field description = fields.Char("Description") class Help(ModelSQL, ModelView): 'GNU Health Help' __name__ = 'gnuhealth.help' ''' This model contains the documentation for the GNU Health Health and Hospital Management System. It can be invoked from the command line or by menu (Help). ''' name = fields.Char("Code", required=True, help="Unique help code") description = fields.Char( "Description", help="Short description", required=True) category = fields.Char("Category") package = fields.Many2One("ir.module", "Package") keywords = fields.Char("Keywords") documentation = fields.Text("Documentation") @classmethod def __setup__(cls): super(Help, cls).__setup__() t = cls.__table__() cls._sql_constraints = [ ('code_unique', Unique(t, t.name), 'The help code already exists') ]
