def get_response(self, req: "CamcopsRequest") -> Response: self.start_datetime = format_datetime( req.get_datetime_param(ViewParam.START_DATETIME), DateFormat.ERA) self.end_datetime = format_datetime( req.get_datetime_param(ViewParam.END_DATETIME), DateFormat.ERA) # noinspection PyUnresolvedReferences return super().get_response(req)
def format_daterange(start: Optional[Pendulum], end: Optional[Pendulum]) -> str: """ Textual representation of an inclusive-to-exclusive date range. Arguments are datetime values. """ return "[{}, {})".format( format_datetime(start, DateFormat.ISO8601_DATE_ONLY, default="−∞"), format_datetime(end, DateFormat.ISO8601_DATE_ONLY, default="+∞") )
def get_dob_html(self, longform: bool) -> str: """ HTML fragment for date of birth. """ if longform: return "<br>Date of birth: {}".format( answer( format_datetime(self.dob, DateFormat.LONG_DATE, default=None))) return "DOB: {}.".format( format_datetime(self.dob, DateFormat.SHORT_DATE))
def as_r(self) -> str: """ Returns data as an R script. This could be more sophisticated, e.g. creating factors with appropriate levels (etc.). """ now = format_datetime( get_now_localtz_pendulum(), DateFormat.ISO8601_HUMANIZED_TO_SECONDS_TZ, ) table_definition_str = "\n\n".join(page.r_data_table_definition() for page in self.pages) script = f"""#!/usr/bin/env Rscript # R script generated by CamCOPS at {now} # ============================================================================= # Libraries # ============================================================================= library(data.table) # ============================================================================= # Data # ============================================================================= {table_definition_str} """ return script
def get_dob_html(self, req: "CamcopsRequest", longform: bool) -> str: """ HTML fragment for date of birth. """ _ = req.gettext if longform: dob = answer( format_datetime(self.dob, DateFormat.LONG_DATE, default=None) ) dobtext = _("Date of birth:") return f"<br>{dobtext} {dob}" else: dobtext = _("DOB:") dob = format_datetime(self.dob, DateFormat.SHORT_DATE) return f"{dobtext} {dob}."
def get_dob_str(self) -> Optional[str]: """ Date of birth, as a string. """ dob_dt = self.get_dob() if dob_dt is None: return None return format_datetime(dob_dt, DateFormat.SHORT_DATE)
def task_collection_to_tsv_zip_response(req: "CamcopsRequest", collection: "TaskCollection", sort_by_heading: bool) -> Response: """ Converts a set of tasks to a TSV (tab-separated value) response, as a set of TSV files (one per table) in a ZIP file. Args: req: a :class:`camcops_server.cc_modules.cc_request.CamcopsRequest` collection: a :class:`camcops_server.cc_modules.cc_taskcollection.TaskCollection` sort_by_heading: sort columns within each page by heading name? Returns: a :class:`pyramid.response.Response` object """ # noqa # ------------------------------------------------------------------------- # Create memory file and ZIP file within it # ------------------------------------------------------------------------- memfile = io.BytesIO() z = zipfile.ZipFile(memfile, "w") # ------------------------------------------------------------------------- # Iterate through tasks # ------------------------------------------------------------------------- audit_descriptions = [] # type: List[str] # Task may return >1 file for TSV output (e.g. for subtables). tsvcoll = TsvCollection() for cls in collection.task_classes(): for task in gen_audited_tasks_for_task_class(collection, cls, audit_descriptions): tsv_pages = task.get_tsv_pages(req) tsvcoll.add_pages(tsv_pages) if sort_by_heading: tsvcoll.sort_headings_within_all_pages() # Write to ZIP. # If there are no valid task instances, there'll be no TSV; that's OK. for filename_stem in tsvcoll.get_page_names(): tsv_filename = filename_stem + ".tsv" tsv_contents = tsvcoll.get_tsv_file(filename_stem) z.writestr(tsv_filename, tsv_contents.encode("utf-8")) # ------------------------------------------------------------------------- # Finish and serve # ------------------------------------------------------------------------- z.close() # Audit audit(req, "Basic dump: {}".format("; ".join(audit_descriptions))) # Return the result zip_contents = memfile.getvalue() memfile.close() zip_filename = "CamCOPS_dump_{}.zip".format( format_datetime(req.now, DateFormat.FILENAME)) return ZipResponse(body=zip_contents, filename=zip_filename)
def _qr_item_answer(self) -> QuestionnaireResponseItemAnswer: """ Returns a QuestionnaireResponseItemAnswer. """ # Look things up raw_answer = self.answer answer_type = self.answer_type # Convert the value if raw_answer is None: # Deal with null values first, otherwise we will get # mis-conversion, e.g. str(None) == "None", bool(None) == False. fhir_answer = None elif answer_type == FHIRAnswerType.BOOLEAN: fhir_answer = bool(raw_answer) elif answer_type == FHIRAnswerType.DATE: fhir_answer = FHIRDate( format_datetime(raw_answer, DateFormat.FHIR_DATE)).as_json() elif answer_type == FHIRAnswerType.DATETIME: fhir_answer = FHIRDate(raw_answer.isoformat()).as_json() elif answer_type == FHIRAnswerType.DECIMAL: fhir_answer = float(raw_answer) elif answer_type == FHIRAnswerType.INTEGER: fhir_answer = int(raw_answer) elif answer_type == FHIRAnswerType.QUANTITY: fhir_answer = Quantity( jsondict={ Fc.VALUE: float(raw_answer) # More sophistication is possible -- units, for example. }).as_json() elif answer_type == FHIRAnswerType.STRING: fhir_answer = str(raw_answer) elif answer_type == FHIRAnswerType.TIME: fhir_answer = FHIRDate( format_datetime(raw_answer, DateFormat.FHIR_TIME)).as_json() elif answer_type == FHIRAnswerType.URI: fhir_answer = str(raw_answer) else: raise NotImplementedError( f"Don't know how to handle FHIR answer type {answer_type}") # Build the FHIR object return QuestionnaireResponseItemAnswer( jsondict={answer_type.value: fhir_answer})
def set_era(self, iso_datetime: str) -> None: from cardinal_pythonlib.datetimefunc import ( convert_datetime_to_utc, format_datetime, ) from camcops_server.cc_modules.cc_constants import DateFormat self.era_time = pendulum.parse(iso_datetime) self.era_time_utc = convert_datetime_to_utc(self.era_time) self.era = format_datetime(self.era_time, DateFormat.ISO8601)
def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]: if not self.is_complete(): return CTV_INCOMPLETE infolist = [ CtvInfo(content="Pertains to: {}. Category: {}.".format( format_datetime(self.date_pertains_to, DateFormat.LONG_DATE), self.get_full_description(req))) ] if self.comments: infolist.append(CtvInfo(content=ws.webify(self.comments))) return infolist
def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]: if not self.is_complete(): return CTV_INCOMPLETE category = (("Meets" if self.meets_criteria() else "Does not meet") + " criteria for mixed affective episode") infolist = [ CtvInfo(content="Pertains to: {}. {}.".format( format_datetime(self.date_pertains_to, DateFormat.LONG_DATE), category)) ] if self.comments: infolist.append(CtvInfo(content=ws.webify(self.comments))) return infolist
def __init__(self, cfg: "CamcopsConfig") -> None: super().__init__() engine = cfg.get_sqla_engine() self.dbsession = sessionmaker()(bind=engine) # type: SqlASession self.era_time = pendulum.now() self.era_time_utc = convert_datetime_to_utc(self.era_time) self.era = format_datetime(self.era_time, DateFormat.ISO8601) self.group = None # type: Optional[Group] self.user = None # type: Optional[User] self.device = None # type: Optional[Device] self.nhs_iddef = None # type: Optional[IdNumDefinition]
def __str__(self) -> str: return ( "Patient(forename={f!r}, surname={sur!r}, sex={sex}, DOB={dob}, " "address={a!r}, gp={gp!r}, other={o!r}, idnums={i})".format( f=self.forename, sur=self.surname, sex=self.sex, dob=format_datetime(self.dob, DateFormat.ISO8601_DATE_ONLY), a=self.address, gp=self.gp, o=self.otherdetails, i="[{}]".format(", ".join( str(idnum) for idnum in self.idnum_definitions)), ))
def get_task_html(self, req: CamcopsRequest) -> str: h = """ <table class="{CssClass.TASKDETAIL}"> <tr> <td width="33%">Location:</td> <td width="67%"><b>{loc}</b></td> </tr> """.format( CssClass=CssClass, loc=ws.webify(self.location), ) h += tr_qa( "Start:", format_datetime(self.start, DateFormat.SHORT_DATETIME, None)) h += tr_qa("End:", format_datetime(self.end, DateFormat.SHORT_DATETIME, None)) h += tr(italic("Calculated duration (hours:minutes)"), italic(get_duration_h_m(self.start, self.end))) h += tr_qa("Patient contact?", get_yes_no_none(req, self.patient_contact)) h += tr_qa("Staff liaison?", get_yes_no_none(req, self.staff_liaison)) h += tr_qa("Other liaison?", get_yes_no_none(req, self.other_liaison)) h += tr_qa("Comment:", self.comment) return h
def get_filename(self, req: "CamcopsRequest", viewtype: str) -> str: extension_dict = { ViewArg.ODS: "ods", ViewArg.TSV: "tsv", ViewArg.XLSX: "xlsx", } if viewtype not in extension_dict: raise HTTPBadRequest("Unsupported viewtype") extension = extension_dict.get(viewtype) return ("CamCOPS_" + self.report_id + "_" + format_datetime(req.now, DateFormat.FILENAME) + "." + extension)
def get_task_html(self, req: CamcopsRequest) -> str: return """ {clinician_comments} <div class="{CssClass.SUMMARY}"> <table class="{CssClass.SUMMARY}"> {tr_is_complete} {date_pertains_to} {meets_criteria} </table> </div> <div class="{CssClass.EXPLANATION}"> {icd10_symptomatic_disclaimer} </div> <table class="{CssClass.TASKDETAIL}"> <tr> <th width="80%">Question</th> <th width="20%">Answer</th> </tr> {mixture_or_rapid_alternation} {duration_at_least_2_weeks} </table> {ICD10_COPYRIGHT_DIV} """.format( clinician_comments=self.get_standard_clinician_comments_block( req, self.comments), CssClass=CssClass, tr_is_complete=self.get_is_complete_tr(req), date_pertains_to=tr_qa( req.wappstring(AS.DATE_PERTAINS_TO), format_datetime(self.date_pertains_to, DateFormat.LONG_DATE, default=None), ), meets_criteria=tr_qa( req.sstring(SS.MEETS_CRITERIA), get_true_false_none(req, self.meets_criteria()), ), icd10_symptomatic_disclaimer=req.wappstring( AS.ICD10_SYMPTOMATIC_DISCLAIMER), mixture_or_rapid_alternation=self.get_twocol_bool_row_true_false( req, "mixture_or_rapid_alternation", self.wxstring(req, "a")), duration_at_least_2_weeks=self.get_twocol_bool_row_true_false( req, "duration_at_least_2_weeks", self.wxstring(req, "b")), ICD10_COPYRIGHT_DIV=ICD10_COPYRIGHT_DIV, )
def get_task_html(self, req: CamcopsRequest) -> str: q_a = self.text_row(req, "a") for i in range(1, self.N_A + 1): q_a += self.get_twocol_bool_row_true_false( req, "a" + str(i), self.wxstring(req, "a" + str(i))) q_a += self.get_twocol_bool_row_true_false(req, "b", self.wxstring(req, "b")) h = """ {clinician_comments} <div class="{CssClass.SUMMARY}"> <table class="{CssClass.SUMMARY}"> {tr_is_complete} {date_pertains_to} {meets_criteria} </table> </div> <table class="{CssClass.TASKDETAIL}"> <tr> <th width="80%">Question</th> <th width="20%">Answer</th> </tr> {q_a} </table> {ICD10_COPYRIGHT_DIV} """.format( clinician_comments=self.get_standard_clinician_comments_block( req, self.comments), CssClass=CssClass, tr_is_complete=self.get_is_complete_tr(req), date_pertains_to=tr_qa( req.wappstring(AS.DATE_PERTAINS_TO), format_datetime(self.date_pertains_to, DateFormat.LONG_DATE, default=None), ), meets_criteria=tr_qa( req.sstring(SS.MEETS_CRITERIA), get_yes_no_none(req, self.meets_criteria()), ), q_a=q_a, ICD10_COPYRIGHT_DIV=ICD10_COPYRIGHT_DIV, ) return h
def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]: if not self.is_complete(): return CTV_INCOMPLETE c = self.meets_general_criteria() if c is None: category = "Unknown if met or not met" elif c: category = "Met" else: category = "Not met" infolist = [ CtvInfo( content=("Pertains to: {}. General criteria for " "schizophrenia: {}.".format( format_datetime(self.date_pertains_to, DateFormat.LONG_DATE), category))) ] if self.comments: infolist.append(CtvInfo(content=ws.webify(self.comments))) return infolist
def get_task_html(self, req: CamcopsRequest) -> str: h = """ <div class="{CssClass.SUMMARY}"> <table class="{CssClass.SUMMARY}"> {tr_is_complete} </table> </div> <table class="{CssClass.TASKDETAIL}"> <col width="25%"> <col width="75%"> """.format( CssClass=CssClass, tr_is_complete=self.get_is_complete_tr(req), ) h += tr_qa( self.wxstring(req, "to"), format_datetime(self.reset_start_time_to, DateFormat.LONG_DATETIME_WITH_DAY, default=None)) h += tr_qa(self.wxstring(req, "reason"), self.reason) h += """ </table> """ return h
def make_obx_segment( req: "CamcopsRequest", task: "Task", task_format: str, observation_identifier: str, observation_datetime: Pendulum, responsible_observer: str, export_options: "TaskExportOptions", ) -> hl7.Segment: # noinspection HttpUrlsUsage """ Creates an HL7 observation result (OBX) segment. - http://www.hl7standards.com/blog/2006/10/18/how-do-i-send-a-binary-file-inside-of-an-hl7-message - http://www.hl7standards.com/blog/2007/11/27/pdf-attachment-in-hl7-message/ - http://www.hl7standards.com/blog/2006/12/01/sending-images-or-formatted-documents-via-hl7-messaging/ - https://www.hl7.org/documentcenter/public/wg/ca/HL7ClmAttIG.PDF - type of data: https://www.hl7.org/implement/standards/fhir/v2/0191/index.html - subtype of data: https://www.hl7.org/implement/standards/fhir/v2/0291/index.html """ # noqa segment_id = "OBX" set_id = str(1) source_application = "CamCOPS" if task_format == FileType.PDF: value_type = "ED" # Encapsulated data (ED) field observation_value = hl7.Field( COMPONENT_SEPARATOR, [ source_application, "Application", # type of data "PDF", # data subtype "Base64", # base 64 encoding base64.standard_b64encode(task.get_pdf(req)), # data ], ) elif task_format == FileType.HTML: value_type = "ED" # Encapsulated data (ED) field observation_value = hl7.Field( COMPONENT_SEPARATOR, [ source_application, "TEXT", # type of data "HTML", # data subtype "A", # no encoding (see table 0299), but need to escape escape_hl7_text(task.get_html(req)), # data ], ) elif task_format == FileType.XML: value_type = "ED" # Encapsulated data (ED) field observation_value = hl7.Field( COMPONENT_SEPARATOR, [ source_application, "TEXT", # type of data "XML", # data subtype "A", # no encoding (see table 0299), but need to escape escape_hl7_text( task.get_xml( req, indent_spaces=0, eol="", options=export_options)), # data ], ) else: raise AssertionError( f"make_obx_segment: invalid task_format: {task_format}") observation_sub_id = "" units = "" reference_range = "" abnormal_flags = "" probability = "" nature_of_abnormal_test = "" observation_result_status = "" date_of_last_observation_normal_values = "" user_defined_access_checks = "" date_and_time_of_observation = format_datetime(observation_datetime, DateFormat.HL7_DATETIME) producer_id = "" observation_method = "" equipment_instance_identifier = "" date_time_of_analysis = "" fields = [ segment_id, set_id, value_type, observation_identifier, observation_sub_id, observation_value, units, reference_range, abnormal_flags, probability, nature_of_abnormal_test, observation_result_status, date_of_last_observation_normal_values, user_defined_access_checks, date_and_time_of_observation, producer_id, responsible_observer, observation_method, equipment_instance_identifier, date_time_of_analysis, ] segment = hl7.Segment(FIELD_SEPARATOR, fields) return segment
def new_era(self) -> str: """ Returns the string used for the new era for this batch, in case we are preserving records. """ return format_datetime(self.batchtime, DateFormat.ERA)
def last_activity_utc_iso(self) -> str: """ Returns a formatted version of the date/time at which the last activity took place for this session. """ return format_datetime(self.last_activity_utc, DateFormat.ISO8601)
def _get_xml( self, audit_string: str, xml_name: str, indent_spaces: int = 4, eol: str = "\n", include_comments: bool = False, ) -> str: """ Returns an XML document representing this object. Args: audit_string: description used to audit access to this information xml_name: name of the root XML element indent_spaces: number of spaces to indent formatted XML eol: end-of-line string include_comments: include comments describing each field? Returns: an XML UTF-8 document representing the task. """ iddef = self.taskfilter.get_only_iddef() if not iddef: raise ValueError("Tracker/CTV doesn't have a single ID number " "criterion") branches = [ self.consistency_info.get_xml_root(), XmlElement( name="_search_criteria", value=[ XmlElement( name="task_tablename_list", value=",".join(self.taskfilter.task_tablename_list), ), XmlElement( name=ViewParam.WHICH_IDNUM, value=iddef.which_idnum, datatype=XmlDataTypes.INTEGER, ), XmlElement( name=ViewParam.IDNUM_VALUE, value=iddef.idnum_value, datatype=XmlDataTypes.INTEGER, ), XmlElement( name=ViewParam.START_DATETIME, value=format_datetime(self.taskfilter.start_datetime, DateFormat.ISO8601), datatype=XmlDataTypes.DATETIME, ), XmlElement( name=ViewParam.END_DATETIME, value=format_datetime(self.taskfilter.end_datetime, DateFormat.ISO8601), datatype=XmlDataTypes.DATETIME, ), ], ), ] options = TaskExportOptions( xml_include_plain_columns=True, xml_include_calculated=True, include_blobs=False, ) for t in self.collection.all_tasks: branches.append(t.get_xml_root(self.req, options)) audit( self.req, audit_string, table=t.tablename, server_pk=t.pk, patient_server_pk=t.get_patient_server_pk(), ) tree = XmlElement(name=xml_name, value=branches) return get_xml_document( tree, indent_spaces=indent_spaces, eol=eol, include_comments=include_comments, )
def get_task_html(self, req: CamcopsRequest) -> str: h = self.get_standard_clinician_comments_block(req, self.comments) h += """ <div class="{CssClass.SUMMARY}"> <table class="{CssClass.SUMMARY}"> """.format(CssClass=CssClass, ) h += self.get_is_complete_tr(req) h += tr_qa( req.wappstring("date_pertains_to"), format_datetime(self.date_pertains_to, DateFormat.LONG_DATE, default=None)) h += tr_qa(self.wxstring(req, "meets_general_criteria"), get_yes_no_none(req, self.has_pd())) h += tr_qa(self.wxstring(req, "paranoid_pd_title"), get_yes_no_none(req, self.has_paranoid_pd())) h += tr_qa(self.wxstring(req, "schizoid_pd_title"), get_yes_no_none(req, self.has_schizoid_pd())) h += tr_qa(self.wxstring(req, "dissocial_pd_title"), get_yes_no_none(req, self.has_dissocial_pd())) h += tr_qa(self.wxstring(req, "eu_pd_i_title"), get_yes_no_none(req, self.has_eupd_i())) h += tr_qa(self.wxstring(req, "eu_pd_b_title"), get_yes_no_none(req, self.has_eupd_b())) h += tr_qa(self.wxstring(req, "histrionic_pd_title"), get_yes_no_none(req, self.has_histrionic_pd())) h += tr_qa(self.wxstring(req, "anankastic_pd_title"), get_yes_no_none(req, self.has_anankastic_pd())) h += tr_qa(self.wxstring(req, "anxious_pd_title"), get_yes_no_none(req, self.has_anxious_pd())) h += tr_qa(self.wxstring(req, "dependent_pd_title"), get_yes_no_none(req, self.has_dependent_pd())) h += """ </table> </div> <div> <p><i>Vignette:</i></p> <p>{vignette}</p> </div> <table class="{CssClass.TASKDETAIL}"> <tr> <th width="80%">Question</th> <th width="20%">Answer</th> </tr> """.format( CssClass=CssClass, vignette=answer(ws.webify(self.vignette), default_for_blank_strings=True), ) # General h += subheading_spanning_two_columns(self.wxstring(req, "general")) h += self.get_twocol_bool_row_true_false(req, "g1", self.wxstring(req, "G1")) h += self.pd_b_text(req, "G1b") for i in range(1, Icd10SpecPD.N_GENERAL_1 + 1): h += self.get_twocol_bool_row_true_false( req, "g1_" + str(i), self.wxstring(req, "G1_" + str(i))) for i in range(2, Icd10SpecPD.N_GENERAL + 1): h += self.get_twocol_bool_row_true_false( req, "g" + str(i), self.wxstring(req, "G" + str(i))) # Paranoid, etc. h += self.standard_pd_html(req, "paranoid", Icd10SpecPD.N_PARANOID) h += self.standard_pd_html(req, "schizoid", Icd10SpecPD.N_SCHIZOID) h += self.standard_pd_html(req, "dissocial", Icd10SpecPD.N_DISSOCIAL) # EUPD is special h += self.pd_heading(req, "eu_pd_title") h += self.pd_skiprow(req, "eu") h += self.pd_general_criteria_bits(req) h += self.pd_subheading(req, "eu_pd_i_title") h += self.pd_b_text(req, "eu_pd_i_B") for i in range(1, Icd10SpecPD.N_EUPD_I + 1): h += self.pd_basic_row(req, "eu", i) h += self.pd_subheading(req, "eu_pd_b_title") h += self.pd_b_text(req, "eu_pd_b_B") for i in range(Icd10SpecPD.N_EUPD_I + 1, Icd10SpecPD.N_EU + 1): h += self.pd_basic_row(req, "eu", i) # Back to plain ones h += self.standard_pd_html(req, "histrionic", Icd10SpecPD.N_HISTRIONIC) h += self.standard_pd_html(req, "anankastic", Icd10SpecPD.N_ANANKASTIC) h += self.standard_pd_html(req, "anxious", Icd10SpecPD.N_ANXIOUS) h += self.standard_pd_html(req, "dependent", Icd10SpecPD.N_DEPENDENT) # Done h += """ </table> """ + ICD10_COPYRIGHT_DIV return h
def get_response(self, req: "CamcopsRequest") -> Response: """ Return the report content itself, as an HTTP :class:`Response`. """ # Check the basic parameters report_id = req.get_str_param(ViewParam.REPORT_ID) rows_per_page = req.get_int_param(ViewParam.ROWS_PER_PAGE, DEFAULT_ROWS_PER_PAGE) page_num = req.get_int_param(ViewParam.PAGE, 1) if report_id != self.report_id: raise HTTPBadRequest("Error - request directed to wrong report!") # viewtype = req.get_str_param(ViewParam.VIEWTYPE, ViewArg.HTML, # lower=True) # ... NO; for a Deform radio button, the request contains parameters # like # ('__start__', 'viewtype:rename'), # ('deformField2', 'tsv'), # ('__end__', 'viewtype:rename') # ... so we need to ask the appstruct instead. # This is a bit different from how we manage trackers/CTVs, where we # recode the appstruct to a URL. # # viewtype = appstruct.get(ViewParam.VIEWTYPE) # type: str # # Ah, no... that fails with pagination of reports. Let's redirect # things to the HTTP query, as for trackers/audit! viewtype = req.get_str_param(ViewParam.VIEWTYPE, ViewArg.HTML, lower=True) if viewtype not in [ViewArg.HTML, ViewArg.TSV]: raise HTTPBadRequest("Bad viewtype") # Run the report (which may take additional parameters from the # request) statement = self.get_query(req) if statement is not None: rp = req.dbsession.execute(statement) # type: ResultProxy column_names = rp.keys() rows = rp.fetchall() else: plain_report = self.get_rows_colnames(req) if plain_report is None: raise NotImplementedError( "Report did not implement either of get_select_statement()" " or get_rows_colnames()") column_names = plain_report.column_names rows = plain_report.rows # Serve the result if viewtype == ViewArg.HTML: page = CamcopsPage(collection=rows, page=page_num, items_per_page=rows_per_page, url_maker=PageUrl(req)) return self.render_html(req=req, column_names=column_names, page=page) else: # TSV filename = ( "CamCOPS_" + self.report_id + "_" + format_datetime(req.now, DateFormat.FILENAME) + ".tsv" ) content = tsv_from_query(rows, column_names) return TsvResponse(body=content, filename=filename)
def get_task_html(self, req: CamcopsRequest) -> str: person_marital_status = get_nhs_dd_person_marital_status(req) ethnic_category_code = get_nhs_dd_ethnic_category_code(req) if self.lps_division == "G": banner_class = CssClass.BANNER_REFERRAL_GENERAL_ADULT division_name = self.wxstring(req, "service_G") elif self.lps_division == "O": banner_class = CssClass.BANNER_REFERRAL_OLD_AGE division_name = self.wxstring(req, "service_O") elif self.lps_division == "S": banner_class = CssClass.BANNER_REFERRAL_SUBSTANCE_MISUSE division_name = self.wxstring(req, "service_S") else: banner_class = "" division_name = None if self.referral_priority == "R": priority_name = self.wxstring(req, "priority_R") elif self.referral_priority == "U": priority_name = self.wxstring(req, "priority_U") elif self.referral_priority == "E": priority_name = self.wxstring(req, "priority_E") else: priority_name = None potential_admission_reasons = [ "admission_reason_overdose", "admission_reason_self_harm_not_overdose", "admission_reason_confusion", "admission_reason_trauma", "admission_reason_falls", "admission_reason_infection", "admission_reason_poor_adherence", "admission_reason_other", ] admission_reasons = [] for r in potential_admission_reasons: if getattr(self, r): admission_reasons.append(self.wxstring(req, "f_" + r)) h = """ <div class="{CssClass.BANNER} {banner_class}">{division_name} referral at {when}</div> <div class="{CssClass.SUMMARY}"> <table class="{CssClass.SUMMARY}"> {tr_is_complete} </table> </div> <table class="{CssClass.TASKDETAIL}"> <col width="25%"> <col width="25%"> <col width="25%"> <col width="25%"> """.format( CssClass=CssClass, banner_class=banner_class, division_name=answer(division_name, default_for_blank_strings=True), when=answer( format_datetime(self.referral_date_time, DateFormat.SHORT_DATETIME_WITH_DAY_NO_TZ, default=None)), tr_is_complete=self.get_is_complete_tr(req), ) h += subheading_spanning_four_columns( self.wxstring(req, "t_about_referral")) h += """ <tr> <td>{q_method}</td> <td>{a_method}</td> <td>{q_priority}</td> <td class="{CssClass.HIGHLIGHT}">{a_priority}</td> </tr> """.format( CssClass=CssClass, q_method=self.wxstring(req, "f_referral_method"), a_method=answer(self.referral_method), q_priority=self.wxstring(req, "f_referral_priority"), a_priority=( answer(self.referral_priority, default_for_blank_strings=True) + # noqa ": " + answer(priority_name))) h += self.four_column_row(self.wxstring(req, "f_referrer_name"), self.referrer_name, self.wxstring(req, "f_referring_specialty"), self.referring_specialty) h += self.four_column_row( self.wxstring(req, "f_referrer_contact_details"), self.referrer_contact_details, self.wxstring(req, "f_referring_specialty_other"), self.referring_specialty_other) h += self.four_column_row(self.wxstring(req, "f_referring_consultant"), self.referring_consultant, "", "") h += subheading_spanning_four_columns(self.wxstring(req, "t_patient")) h += """ <tr> <td>{q_when}</td> <td>{a_when}</td> <td>{q_where}</td> <td class="{CssClass.HIGHLIGHT}">{a_where}</td> </tr> """.format( CssClass=CssClass, q_when=self.wxstring(req, "f_admission_date"), a_when=answer( format_datetime(self.admission_date, DateFormat.LONG_DATE, default=None), ""), q_where=self.wxstring(req, "f_patient_location"), a_where=answer(self.patient_location), ) h += self.four_column_row( self.wxstring(req, "f_estimated_discharge_date"), format_datetime(self.estimated_discharge_date, DateFormat.LONG_DATE, ""), self.wxstring(req, "f_patient_aware_of_referral"), get_yes_no_none(req, self.patient_aware_of_referral)) h += self.four_column_row( self.wxstring(req, "f_marital_status"), person_marital_status.get(self.marital_status_code, INVALID_VALUE), self.wxstring(req, "f_interpreter_required"), get_yes_no_none(req, self.interpreter_required)) h += self.four_column_row( self.wxstring(req, "f_ethnic_category"), ethnic_category_code.get(self.ethnic_category_code, INVALID_VALUE), self.wxstring(req, "f_sensory_impairment"), get_yes_no_none(req, self.sensory_impairment)) h += subheading_spanning_four_columns( self.wxstring(req, "t_admission_reason")) h += tr_span_col(answer(", ".join(admission_reasons), ""), cols=4) h += subheading_spanning_four_columns( self.wxstring(req, "t_other_people")) h += self.tr_qa(self.wxstring(req, "f_existing_psychiatric_teams"), self.existing_psychiatric_teams, "") h += self.tr_qa(self.wxstring(req, "f_care_coordinator"), self.care_coordinator, "") h += self.tr_qa(self.wxstring(req, "f_other_contact_details"), self.other_contact_details, "") h += subheading_spanning_four_columns( self.wxstring(req, "t_referral_reason")) h += tr_span_col(answer(self.referral_reason, ""), cols=4) h += """ </table> """ return h
def task_collection_to_sqlite_response(req: "CamcopsRequest", collection: "TaskCollection", export_options: TaskExportOptions, as_sql_not_binary: bool) -> Response: """ Converts a set of tasks to an SQLite export, either as binary or the SQL text to regenerate it. Args: req: a :class:`camcops_server.cc_modules.cc_request.CamcopsRequest` collection: a :class:`camcops_server.cc_modules.cc_taskcollection.TaskCollection` export_options: a :class:`TaskExportOptions` object as_sql_not_binary: provide SQL text, rather than SQLite binary? Returns: a :class:`pyramid.response.Response` object """ # noqa # ------------------------------------------------------------------------- # Create memory file, dumper, and engine # ------------------------------------------------------------------------- # This approach failed: # # memfile = io.StringIO() # # def dump(querysql, *multiparams, **params): # compsql = querysql.compile(dialect=engine.dialect) # memfile.write("{};\n".format(compsql)) # # engine = create_engine('{dialect}://'.format(dialect=dialect_name), # strategy='mock', executor=dump) # dst_session = sessionmaker(bind=engine)() # type: SqlASession # # ... you get the error # AttributeError: 'MockConnection' object has no attribute 'begin' # ... which is fair enough. # # Next best thing: SQLite database. # Two ways to deal with it: # (a) duplicate our C++ dump code (which itself duplicate the SQLite # command-line executable's dump facility), then create the database, # dump it to a string, serve the string; or # (b) offer the binary SQLite file. # Or... (c) both. # Aha! pymysqlite.iterdump does this for us. # # If we create an in-memory database using create_engine('sqlite://'), # can we get the binary contents out? Don't think so. # # So we should first create a temporary on-disk file, then use that. # ------------------------------------------------------------------------- # Make temporary file (one whose filename we can know). # We use tempfile.mkstemp() for security, or NamedTemporaryFile, # which is a bit easier. However, you can't necessarily open the file # again under all OSs, so that's no good. The final option is # TemporaryDirectory, which is secure and convenient. # # https://docs.python.org/3/library/tempfile.html # https://security.openstack.org/guidelines/dg_using-temporary-files-securely.html # noqa # https://stackoverflow.com/questions/3924117/how-to-use-tempfile-namedtemporaryfile-in-python # noqa # ------------------------------------------------------------------------- db_basename = "temp.sqlite3" with tempfile.TemporaryDirectory() as tmpdirname: db_filename = os.path.join(tmpdirname, db_basename) # --------------------------------------------------------------------- # Make SQLAlchemy session # --------------------------------------------------------------------- url = "sqlite:///" + db_filename engine = create_engine(url, echo=False) dst_session = sessionmaker(bind=engine)() # type: SqlASession # --------------------------------------------------------------------- # Iterate through tasks, creating tables as we need them. # --------------------------------------------------------------------- audit_descriptions = [] # type: List[str] task_generator = gen_audited_tasks_by_task_class( collection, audit_descriptions) # --------------------------------------------------------------------- # Next bit very tricky. We're trying to achieve several things: # - a copy of part of the database structure # - a copy of part of the data, with relationships intact # - nothing sensitive (e.g. full User records) going through # - adding new columns for Task objects offering summary values # - Must treat tasks all together, because otherwise we will insert # duplicate dependency objects like Group objects. # --------------------------------------------------------------------- copy_tasks_and_summaries(tasks=task_generator, dst_engine=engine, dst_session=dst_session, export_options=export_options, req=req) dst_session.commit() # --------------------------------------------------------------------- # Audit # --------------------------------------------------------------------- audit(req, "SQL dump: {}".format("; ".join(audit_descriptions))) # --------------------------------------------------------------------- # Fetch file contents, either as binary, or as SQL # --------------------------------------------------------------------- filename_stem = "CamCOPS_dump_{}".format( format_datetime(req.now, DateFormat.FILENAME)) suggested_filename = filename_stem + (".sql" if as_sql_not_binary else ".sqlite3") if as_sql_not_binary: # SQL text con = sqlite3.connect(db_filename) with io.StringIO() as f: # noinspection PyTypeChecker for line in con.iterdump(): f.write(line + "\n") con.close() f.flush() sql_text = f.getvalue() return TextAttachmentResponse(body=sql_text, filename=suggested_filename) else: # SQLite binary with open(db_filename, 'rb') as f: binary_contents = f.read() return SqliteBinaryResponse(body=binary_contents, filename=suggested_filename)
def get_task_html(self, req: CamcopsRequest) -> str: h = """ <div class="{CssClass.SUMMARY}"> <table class="{CssClass.SUMMARY}"> {tr_is_complete} </table> </div> <table class="{CssClass.TASKDETAIL}"> <col width="40%"> <col width="60%"> """.format( CssClass=CssClass, tr_is_complete=self.get_is_complete_tr(req), ) h += tr_qa( self.wxstring(req, "discharge_date"), format_datetime(self.discharge_date, DateFormat.LONG_DATE_WITH_DAY, default=None), "") h += tr_qa(self.wxstring(req, "discharge_reason"), self.get_discharge_reason(req), "") h += tr_qa(self.wxstring(req, "leaflet_or_discharge_card_given"), get_yes_no_none(req, self.leaflet_or_discharge_card_given), "") h += tr_qa(self.wxstring(req, "frequent_attender"), get_yes_no_none(req, self.frequent_attender), "") h += tr_qa(self.wxstring(req, "patient_wanted_copy_of_letter"), self.patient_wanted_copy_of_letter, "") h += tr_qa(self.wxstring(req, "gaf_at_first_assessment"), self.gaf_at_first_assessment, "") h += tr_qa(self.wxstring(req, "gaf_at_discharge"), self.gaf_at_discharge, "") h += subheading_spanning_two_columns( self.wxstring(req, "referral_reason_t")) h += tr_span_col(answer(", ".join(self.get_referral_reasons(req))), cols=2) h += tr_qa(self.wxstring(req, "referral_reason_transplant_organ"), self.referral_reason_transplant_organ, "") h += tr_qa(self.wxstring(req, "referral_reason_other_detail"), self.referral_reason_other_detail, "") h += subheading_spanning_two_columns(self.wxstring(req, "diagnoses_t")) h += tr_qa(self.wxstring(req, "psychiatric_t"), "\n".join(self.get_psychiatric_diagnoses(req)), "") h += tr_qa(self.wxstring(req, "medical_t"), "\n".join(self.get_medical_diagnoses()), "") h += subheading_spanning_two_columns(self.wxstring( req, "management_t")) h += tr_span_col(answer(", ".join(self.get_managements(req))), cols=2) h += tr_qa(self.wxstring(req, "management_other_detail"), self.management_other_detail, "") h += subheading_spanning_two_columns(self.wxstring(req, "outcome_t")) h += tr_qa(self.wxstring(req, "outcome_t"), self.outcome, "") h += tr_qa(self.wxstring(req, "outcome_hospital_transfer_detail"), self.outcome_hospital_transfer_detail, "") h += tr_qa(self.wxstring(req, "outcome_other_detail"), self.outcome_other_detail, "") h += """ </table> """ return h
def get_fhir_bundle_entry( self, req: "CamcopsRequest", recipient: "ExportRecipient" ) -> Dict[str, Any]: """ Returns a dictionary, suitable for serializing to JSON, that encapsulates patient identity information in a FHIR bundle. See https://www.hl7.org/fhir/patient.html. """ # The JSON objects we will build up: patient_dict = {} # type: JsonObjectType # Name if self.forename or self.surname: name_dict = {} # type: JsonObjectType if self.forename: name_dict[Fc.NAME_GIVEN] = [self.forename] if self.surname: name_dict[Fc.NAME_FAMILY] = self.surname patient_dict[Fc.NAME] = [HumanName(jsondict=name_dict).as_json()] # DOB if self.dob: patient_dict[Fc.BIRTHDATE] = format_datetime( self.dob, DateFormat.FILENAME_DATE_ONLY ) # Sex/gender (should always be present, per client minimum ID policy) if self.sex: gender_lookup = { SEX_FEMALE: Fc.GENDER_FEMALE, SEX_MALE: Fc.GENDER_MALE, SEX_OTHER_UNSPECIFIED: Fc.GENDER_OTHER, } patient_dict[Fc.GENDER] = gender_lookup.get( self.sex, Fc.GENDER_UNKNOWN ) # Address if self.address: patient_dict[Fc.ADDRESS] = [ Address(jsondict={Fc.ADDRESS_TEXT: self.address}).as_json() ] # Email if self.email: patient_dict[Fc.TELECOM] = [ ContactPoint( jsondict={ Fc.SYSTEM: Fc.TELECOM_SYSTEM_EMAIL, Fc.VALUE: self.email, } ).as_json() ] # General practitioner (GP): via # fhirclient.models.fhirreference.FHIRReference; too structured. # ID numbers go here: return make_fhir_bundle_entry( resource_type_url=Fc.RESOURCE_TYPE_PATIENT, identifier=self.get_fhir_identifier(req, recipient), resource=FhirPatient(jsondict=patient_dict).as_json(), )
def make_dg1_segment( set_id: int, diagnosis_datetime: Pendulum, coding_system: str, diagnosis_identifier: str, diagnosis_text: str, alternate_coding_system: str = "", alternate_diagnosis_identifier: str = "", alternate_diagnosis_text: str = "", diagnosis_type: str = "F", diagnosis_classification: str = "D", confidential_indicator: str = "N", clinician_id_number: Union[str, int] = None, clinician_surname: str = "", clinician_forename: str = "", clinician_middle_name_or_initial: str = "", clinician_suffix: str = "", clinician_prefix: str = "", clinician_degree: str = "", clinician_source_table: str = "", clinician_assigning_authority: str = "", clinician_name_type_code: str = "", clinician_identifier_type_code: str = "", clinician_assigning_facility: str = "", attestation_datetime: Pendulum = None, ) -> hl7.Segment: # noinspection HttpUrlsUsage """ Creates an HL7 diagnosis (DG1) segment. Args: .. code-block:: none set_id: Diagnosis sequence number, starting with 1 (use higher numbers for >1 diagnosis). diagnosis_datetime: Date/time diagnosis was made. coding_system: E.g. "I9C" for ICD9-CM; "I10" for ICD10. diagnosis_identifier: Code. diagnosis_text: Text. alternate_coding_system: Optional alternate coding system. alternate_diagnosis_identifier: Optional alternate code. alternate_diagnosis_text: Optional alternate text. diagnosis_type: A admitting, W working, F final. diagnosis_classification: C consultation, D diagnosis, M medication, O other, R radiological scheduling, S sign and symptom, T tissue diagnosis, I invasive procedure not classified elsewhere. confidential_indicator: Y yes, N no clinician_id_number: } Diagnosing clinician. clinician_surname: } clinician_forename: } clinician_middle_name_or_initial: } clinician_suffix: } clinician_prefix: } clinician_degree: } clinician_source_table: } clinician_assigning_authority: } clinician_name_type_code: } clinician_identifier_type_code: } clinician_assigning_facility: } attestation_datetime: Date/time the diagnosis was attested. - http://www.mexi.be/documents/hl7/ch600012.htm - https://www.hl7.org/special/committees/vocab/V26_Appendix_A.pdf """ segment_id = "DG1" try: int(set_id) set_id = str(set_id) except Exception: raise AssertionError("make_dg1_segment: set_id invalid") diagnosis_coding_method = "" diagnosis_code = hl7.Field( COMPONENT_SEPARATOR, [ diagnosis_identifier, diagnosis_text, coding_system, alternate_diagnosis_identifier, alternate_diagnosis_text, alternate_coding_system, ], ) diagnosis_description = "" diagnosis_datetime = format_datetime(diagnosis_datetime, DateFormat.HL7_DATETIME) if diagnosis_type not in ("A", "W", "F"): raise AssertionError("make_dg1_segment: diagnosis_type invalid") major_diagnostic_category = "" diagnostic_related_group = "" drg_approval_indicator = "" drg_grouper_review_code = "" outlier_type = "" outlier_days = "" outlier_cost = "" grouper_version_and_type = "" diagnosis_priority = "" try: clinician_id_number = (str(int(clinician_id_number)) if clinician_id_number is not None else "") except Exception: raise AssertionError("make_dg1_segment: diagnosing_clinician_id_number" " invalid") if clinician_id_number: clinician_id_check_digit = get_mod11_checkdigit(clinician_id_number) clinician_checkdigit_scheme = "M11" # Mod 11 algorithm else: clinician_id_check_digit = "" clinician_checkdigit_scheme = "" diagnosing_clinician = hl7.Field( COMPONENT_SEPARATOR, [ clinician_id_number, clinician_surname or "", clinician_forename or "", clinician_middle_name_or_initial or "", clinician_suffix or "", clinician_prefix or "", clinician_degree or "", clinician_source_table or "", clinician_assigning_authority or "", clinician_name_type_code or "", clinician_id_check_digit or "", clinician_checkdigit_scheme or "", clinician_identifier_type_code or "", clinician_assigning_facility or "", ], ) if diagnosis_classification not in ( "C", "D", "M", "O", "R", "S", "T", "I", ): raise AssertionError( "make_dg1_segment: diagnosis_classification invalid") if confidential_indicator not in ("Y", "N"): raise AssertionError( "make_dg1_segment: confidential_indicator invalid") attestation_datetime = (format_datetime(attestation_datetime, DateFormat.HL7_DATETIME) if attestation_datetime else "") fields = [ segment_id, set_id, diagnosis_coding_method, diagnosis_code, diagnosis_description, diagnosis_datetime, diagnosis_type, major_diagnostic_category, diagnostic_related_group, drg_approval_indicator, drg_grouper_review_code, outlier_type, outlier_days, outlier_cost, grouper_version_and_type, diagnosis_priority, diagnosing_clinician, diagnosis_classification, confidential_indicator, attestation_datetime, ] segment = hl7.Segment(FIELD_SEPARATOR, fields) return segment