Source code for camcops_server.tasks.psychiatricclerking

"""
camcops_server/tasks/psychiatricclerking.py

===============================================================================

    Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
    Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

    This file is part of CamCOPS.

    CamCOPS 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.

    CamCOPS 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 CamCOPS. If not, see <https://www.gnu.org/licenses/>.

===============================================================================

"""

from typing import Dict, List, Optional

import cardinal_pythonlib.rnc_web as ws
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.sql.sqltypes import UnicodeText

from camcops_server.cc_modules.cc_constants import CssClass
from camcops_server.cc_modules.cc_ctvinfo import CtvInfo
from camcops_server.cc_modules.cc_request import CamcopsRequest
from camcops_server.cc_modules.cc_snomed import (
    SnomedConcept,
    SnomedExpression,
    SnomedLookup,
)
from camcops_server.cc_modules.cc_sqlalchemy import Base
from camcops_server.cc_modules.cc_task import (
    Task,
    TaskHasClinicianMixin,
    TaskHasPatientMixin,
)


# =============================================================================
# PsychiatricClerking
# =============================================================================


[docs]class PsychiatricClerking( # type: ignore[misc] TaskHasPatientMixin, TaskHasClinicianMixin, Task, Base ): """ Server implementation of the Clerking task. """ __tablename__ = "psychiatricclerking" shortname = "Clerking" info_filename_stem = "clinical" # FIELDSPEC_A = CLINICIAN_FIELDSPECS # replaced by has_clinician, then by TaskHasClinicianMixin # noqa location: Mapped[Optional[str]] = mapped_column("location", UnicodeText) contact_type: Mapped[Optional[str]] = mapped_column(UnicodeText) reason_for_contact: Mapped[Optional[str]] = mapped_column(UnicodeText) presenting_issue: Mapped[Optional[str]] = mapped_column(UnicodeText) systems_review: Mapped[Optional[str]] = mapped_column(UnicodeText) collateral_history: Mapped[Optional[str]] = mapped_column(UnicodeText) diagnoses_psychiatric: Mapped[Optional[str]] = mapped_column(UnicodeText) diagnoses_medical: Mapped[Optional[str]] = mapped_column(UnicodeText) operations_procedures: Mapped[Optional[str]] = mapped_column(UnicodeText) allergies_adverse_reactions: Mapped[Optional[str]] = mapped_column( UnicodeText ) medications: Mapped[Optional[str]] = mapped_column(UnicodeText) recreational_drug_use: Mapped[Optional[str]] = mapped_column(UnicodeText) family_history: Mapped[Optional[str]] = mapped_column(UnicodeText) developmental_history: Mapped[Optional[str]] = mapped_column(UnicodeText) personal_history: Mapped[Optional[str]] = mapped_column(UnicodeText) premorbid_personality: Mapped[Optional[str]] = mapped_column(UnicodeText) forensic_history: Mapped[Optional[str]] = mapped_column(UnicodeText) current_social_situation: Mapped[Optional[str]] = mapped_column( UnicodeText ) mse_appearance_behaviour: Mapped[Optional[str]] = mapped_column( UnicodeText ) mse_speech: Mapped[Optional[str]] = mapped_column(UnicodeText) mse_mood_subjective: Mapped[Optional[str]] = mapped_column(UnicodeText) mse_mood_objective: Mapped[Optional[str]] = mapped_column(UnicodeText) mse_thought_form: Mapped[Optional[str]] = mapped_column(UnicodeText) mse_thought_content: Mapped[Optional[str]] = mapped_column(UnicodeText) mse_perception: Mapped[Optional[str]] = mapped_column(UnicodeText) mse_cognition: Mapped[Optional[str]] = mapped_column(UnicodeText) mse_insight: Mapped[Optional[str]] = mapped_column(UnicodeText) physical_examination_general: Mapped[Optional[str]] = mapped_column( UnicodeText ) physical_examination_cardiovascular: Mapped[Optional[str]] = mapped_column( UnicodeText ) physical_examination_respiratory: Mapped[Optional[str]] = mapped_column( UnicodeText ) physical_examination_abdominal: Mapped[Optional[str]] = mapped_column( UnicodeText ) physical_examination_neurological: Mapped[Optional[str]] = mapped_column( UnicodeText ) assessment_scales: Mapped[Optional[str]] = mapped_column(UnicodeText) investigations_results: Mapped[Optional[str]] = mapped_column(UnicodeText) safety_alerts: Mapped[Optional[str]] = mapped_column(UnicodeText) risk_assessment: Mapped[Optional[str]] = mapped_column(UnicodeText) relevant_legal_information: Mapped[Optional[str]] = mapped_column( UnicodeText ) current_problems: Mapped[Optional[str]] = mapped_column(UnicodeText) patient_carer_concerns: Mapped[Optional[str]] = mapped_column(UnicodeText) impression: Mapped[Optional[str]] = mapped_column(UnicodeText) management_plan: Mapped[Optional[str]] = mapped_column(UnicodeText) information_given: Mapped[Optional[str]] = mapped_column(UnicodeText) FIELDS_B = [ "location", "contact_type", "reason_for_contact", "presenting_issue", "systems_review", "collateral_history", ] FIELDS_C = [ "diagnoses_psychiatric", "diagnoses_medical", "operations_procedures", "allergies_adverse_reactions", "medications", "recreational_drug_use", "family_history", "developmental_history", "personal_history", "premorbid_personality", "forensic_history", "current_social_situation", ] FIELDS_MSE = [ "mse_appearance_behaviour", "mse_speech", "mse_mood_subjective", "mse_mood_objective", "mse_thought_form", "mse_thought_content", "mse_perception", "mse_cognition", "mse_insight", ] FIELDS_PE = [ "physical_examination_general", "physical_examination_cardiovascular", "physical_examination_respiratory", "physical_examination_abdominal", "physical_examination_neurological", ] FIELDS_D = ["assessment_scales", "investigations_results"] FIELDS_E = [ "safety_alerts", "risk_assessment", "relevant_legal_information", ] FIELDS_F = [ "current_problems", "patient_carer_concerns", "impression", "management_plan", "information_given", ]
[docs] @staticmethod def longname(req: "CamcopsRequest") -> str: _ = req.gettext return _("Psychiatric clerking")
def get_ctv_heading( self, req: CamcopsRequest, wstringname: str ) -> CtvInfo: return CtvInfo( heading=self.wxstring(req, wstringname), skip_if_no_content=False ) def get_ctv_subheading( self, req: CamcopsRequest, wstringname: str ) -> CtvInfo: return CtvInfo( subheading=self.wxstring(req, wstringname), skip_if_no_content=False, ) def get_ctv_description_content( self, req: CamcopsRequest, x: str ) -> CtvInfo: return CtvInfo( description=self.wxstring(req, x), content=ws.webify(getattr(self, x)), )
[docs] def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]: infolist = [self.get_ctv_heading(req, "heading_current_contact")] for x in self.FIELDS_B: infolist.append(self.get_ctv_description_content(req, x)) infolist.append(self.get_ctv_heading(req, "heading_background")) for x in self.FIELDS_C: infolist.append(self.get_ctv_description_content(req, x)) infolist.append( self.get_ctv_heading(req, "heading_examination_investigations") ) infolist.append( self.get_ctv_subheading(req, "mental_state_examination") ) for x in self.FIELDS_MSE: infolist.append(self.get_ctv_description_content(req, x)) infolist.append(self.get_ctv_subheading(req, "physical_examination")) for x in self.FIELDS_PE: infolist.append(self.get_ctv_description_content(req, x)) infolist.append( self.get_ctv_subheading(req, "assessments_and_investigations") ) for x in self.FIELDS_D: infolist.append(self.get_ctv_description_content(req, x)) infolist.append(self.get_ctv_heading(req, "heading_risk_legal")) for x in self.FIELDS_E: infolist.append(self.get_ctv_description_content(req, x)) infolist.append(self.get_ctv_heading(req, "heading_summary_plan")) for x in self.FIELDS_F: infolist.append(self.get_ctv_description_content(req, x)) return infolist
# noinspection PyMethodOverriding
[docs] @staticmethod def is_complete() -> bool: return True
def heading(self, req: CamcopsRequest, wstringname: str) -> str: return '<div class="{CssClass.HEADING}">{s}</div>'.format( CssClass=CssClass, s=self.wxstring(req, wstringname) ) def subheading(self, req: CamcopsRequest, wstringname: str) -> str: return '<div class="{CssClass.SUBHEADING}">{s}</div>'.format( CssClass=CssClass, s=self.wxstring(req, wstringname) ) def subsubheading(self, req: CamcopsRequest, wstringname: str) -> str: return '<div class="{CssClass.SUBSUBHEADING}">{s}</div>'.format( CssClass=CssClass, s=self.wxstring(req, wstringname) ) def subhead_text(self, req: CamcopsRequest, fieldname: str) -> str: return self.subheading(req, fieldname) + "<div><b>{}</b></div>".format( ws.webify(getattr(self, fieldname)) ) def subsubhead_text(self, req: CamcopsRequest, fieldname: str) -> str: return ( self.subsubheading(req, fieldname) + f"<div><b>{ws.webify(getattr(self, fieldname))}</b></div>" )
[docs] def get_task_html(self, req: CamcopsRequest) -> str: # Avoid tables - PDF generator crashes if text is too long. html = "" html += self.heading(req, "heading_current_contact") for x in self.FIELDS_B: html += self.subhead_text(req, x) html += self.heading(req, "heading_background") for x in self.FIELDS_C: html += self.subhead_text(req, x) html += self.heading(req, "heading_examination_investigations") html += self.subheading(req, "mental_state_examination") for x in self.FIELDS_MSE: html += self.subsubhead_text(req, x) html += self.subheading(req, "physical_examination") for x in self.FIELDS_PE: html += self.subsubhead_text(req, x) for x in self.FIELDS_D: html += self.subhead_text(req, x) html += self.heading(req, "heading_risk_legal") for x in self.FIELDS_E: html += self.subhead_text(req, x) html += self.heading(req, "heading_summary_plan") for x in self.FIELDS_F: html += self.subhead_text(req, x) return html
[docs] def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]: refinement = {} # type: Dict[SnomedConcept, str] def add(snomed_lookup: str, contents: Optional[str]) -> None: if not contents: return nonlocal refinement concept = req.snomed(snomed_lookup) refinement[concept] = contents # not location # not contact type add(SnomedLookup.PSYCLERK_REASON_FOR_REFERRAL, self.reason_for_contact) add(SnomedLookup.PSYCLERK_PRESENTING_ISSUE, self.presenting_issue) add(SnomedLookup.PSYCLERK_SYSTEMS_REVIEW, self.systems_review) add(SnomedLookup.PSYCLERK_COLLATERAL_HISTORY, self.collateral_history) add( SnomedLookup.PSYCLERK_PAST_MEDICAL_SURGICAL_MENTAL_HEALTH_HISTORY, self.diagnoses_medical, ) add( SnomedLookup.PSYCLERK_PAST_MEDICAL_SURGICAL_MENTAL_HEALTH_HISTORY, self.diagnoses_psychiatric, ) add(SnomedLookup.PSYCLERK_PROCEDURES, self.operations_procedures) add( SnomedLookup.PSYCLERK_ALLERGIES_ADVERSE_REACTIONS, self.allergies_adverse_reactions, ) add( SnomedLookup.PSYCLERK_MEDICATIONS_MEDICAL_DEVICES, self.medications ) add( SnomedLookup.PSYCLERK_DRUG_SUBSTANCE_USE, self.recreational_drug_use, ) add(SnomedLookup.PSYCLERK_FAMILY_HISTORY, self.family_history) add( SnomedLookup.PSYCLERK_DEVELOPMENTAL_HISTORY, self.developmental_history, ) add( SnomedLookup.PSYCLERK_SOCIAL_PERSONAL_HISTORY, self.personal_history, ) add(SnomedLookup.PSYCLERK_PERSONALITY, self.premorbid_personality) add( SnomedLookup.PSYCLERK_PRISON_RECORD_CRIMINAL_ACTIVITY, self.forensic_history, ) add( SnomedLookup.PSYCLERK_SOCIAL_HISTORY_BASELINE, self.current_social_situation, ) add( SnomedLookup.PSYCLERK_MSE_APPEARANCE, self.mse_appearance_behaviour ) # duplication add( SnomedLookup.PSYCLERK_MSE_BEHAVIOUR, self.mse_appearance_behaviour ) # duplication add(SnomedLookup.PSYCLERK_MSE_MOOD, self.mse_mood_subjective) # close add(SnomedLookup.PSYCLERK_MSE_AFFECT, self.mse_mood_objective) # ... Logic here: "objective mood" is certainly affect (emotional # weather). "Subjective mood" is both mood (emotional climate) and # affect. Not perfect, but reasonable. add(SnomedLookup.PSYCLERK_MSE_THOUGHT, self.mse_thought_form) add(SnomedLookup.PSYCLERK_MSE_THOUGHT, self.mse_thought_content) # ... No way of disambiguating the two in SNOMED-CT. add(SnomedLookup.PSYCLERK_MSE_PERCEPTION, self.mse_perception) add(SnomedLookup.PSYCLERK_MSE_COGNITION, self.mse_cognition) add(SnomedLookup.PSYCLERK_MSE_INSIGHT, self.mse_insight) add( SnomedLookup.PSYCLERK_PHYSEXAM_GENERAL, self.physical_examination_general, ) add( SnomedLookup.PSYCLERK_PHYSEXAM_CARDIOVASCULAR, self.physical_examination_cardiovascular, ) add( SnomedLookup.PSYCLERK_PHYSEXAM_RESPIRATORY, self.physical_examination_respiratory, ) add( SnomedLookup.PSYCLERK_PHYSEXAM_ABDOMINAL, self.physical_examination_abdominal, ) add( SnomedLookup.PSYCLERK_PHYSEXAM_NEUROLOGICAL, self.physical_examination_neurological, ) add(SnomedLookup.PSYCLERK_ASSESSMENT_SCALES, self.assessment_scales) add( SnomedLookup.PSYCLERK_INVESTIGATIONS_RESULTS, self.investigations_results, ) add(SnomedLookup.PSYCLERK_SAFETY_ALERTS, self.safety_alerts) add(SnomedLookup.PSYCLERK_RISK_ASSESSMENT, self.risk_assessment) add( SnomedLookup.PSYCLERK_RELEVANT_LEGAL_INFORMATION, self.relevant_legal_information, ) add(SnomedLookup.PSYCLERK_CURRENT_PROBLEMS, self.current_problems) add( SnomedLookup.PSYCLERK_PATIENT_CARER_CONCERNS, self.patient_carer_concerns, ) add(SnomedLookup.PSYCLERK_CLINICAL_NARRATIVE, self.impression) add(SnomedLookup.PSYCLERK_MANAGEMENT_PLAN, self.management_plan) add(SnomedLookup.PSYCLERK_INFORMATION_GIVEN, self.information_given) codes = [ SnomedExpression( req.snomed( SnomedLookup.DIAGNOSTIC_PSYCHIATRIC_INTERVIEW_PROCEDURE ), refinement=refinement or None, # type: ignore[arg-type] ) ] return codes