示例#1
0
    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)
示例#2
0
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="+∞")
    )
示例#3
0
 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))
示例#4
0
    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
示例#5
0
    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}."
示例#6
0
 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)
示例#7
0
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)
示例#8
0
    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})
示例#9
0
    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)
示例#10
0
 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
示例#11
0
 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
示例#12
0
    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]
示例#13
0
 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)),
         ))
示例#14
0
 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
示例#15
0
    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)
示例#16
0
 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,
     )
示例#17
0
 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
示例#18
0
 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
示例#19
0
 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
示例#20
0
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
示例#21
0
 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)
示例#22
0
 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)
示例#23
0
    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,
        )
示例#24
0
    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
示例#25
0
    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)
示例#26
0
    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
示例#27
0
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)
示例#28
0
    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
示例#29
0
    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(),
        )
示例#30
0
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