Custom Report Schema
Custom reports define tabular views of clinical data for a patient cohort. They are referenced from Custom Dashboards by identifier. Five report types are available, each with its own root element and a schemaVersion="3" attribute.
The <rows> element contains a patient query that defines the cohort. See the Patient Query Language reference for full documentation of population sources, code lists, variables, and rule expressions.
| Report type | Root element | Row type |
|---|---|---|
| Patient report | <patientReport> | One row per matching patient |
| Appointment report | <appointmentReport> | One row per matching appointment |
| Prescription report | <prescriptionReport> | One row per matching prescription |
| Prescription issue report | <prescriptionIssueReport> | One row per matching prescription issue |
| Activity report | <activityReport> | One row per matching clinical activity |
<patientReport> also takes an optional focus attribute (patients or
identifiers), recording which of the two patient-shaped visual builder modes
created the report. Both modes produce the same columns and the same
population, so focus doesn't change what the report returns, it only lets
the builder reopen the report in the mode that created it. Reports saved
before this attribute existed have no focus and still open.
Example
<?xml version="1.0" encoding="UTF-8"?>
<patientReport schemaVersion="3">
<name>Patients with Diabetes</name>
<rows>
<patientQuery>
<name>Diabetes cohort</name>
<description>Patients with an active diabetes code.</description>
<population>
<patientQueryReference>ALL</patientQueryReference>
</population>
<codeLists>
<codeList>
<name>DIABETES</name>
<codeListDefinition>
<rules>
<inclusion><< 73211009 |Diabetes mellitus|</inclusion>
</rules>
</codeListDefinition>
</codeList>
</codeLists>
<variables>
<codedEntryVariable name="DIABETES_DAT" code="DIABETES"
select="clinically_relevant_date"
order="clinically_relevant_date"
take="latest"/>
</variables>
<rules>
<rule>
<if>DIABETES_DAT NOTEQ NULL</if>
<then>select</then>
<else>reject</else>
</rule>
</rules>
</patientQuery>
</rows>
<columns>
<demographicInformationColumn value="patientName"/>
<demographicInformationColumn value="patientNhsNumber"/>
<demographicInformationColumn value="patientDateOfBirth"/>
<demographicInformationColumn value="namedGP"/>
</columns>
</patientReport>
To reference a patient query already published to Medicus rather than embedding one inline, use patientQueryReference inside population:
<rows>
<patientQuery>
<name>Diabetes cohort</name>
<description>Patients matched by the published diabetes query.</description>
<population>
<patientQueryReference>my-org/diabetes-patients-2025</patientQueryReference>
</population>
<codeLists/>
<variables/>
<rules>
<rule>
<if>true</if>
<then>select</then>
<else>reject</else>
</rule>
</rules>
</patientQuery>
</rows>
Unlike patient queries, a custom report has no user-chosen identifier. To reference one report's cohort as another's population, use patientReportReference with the target report's internal ID, the UUID shown in its view URL (/reporting/data/custom-report/view/{customReportId}):
<rows>
<patientQuery>
<name>Diabetes cohort</name>
<description>Patients matched by an existing report.</description>
<population>
<patientReportReference>4e1e3f2a-1234-4c1a-9a2b-abcdef123456</patientReportReference>
</population>
<codeLists/>
<variables/>
<rules>
<rule>
<if>true</if>
<then>select</then>
<else>reject</else>
</rule>
</rules>
</patientQuery>
</rows>
Worked examples
These examples are checked automatically against the current schema (npm run check-schema-examples, wired into prebuild) - if a schema change breaks one, the build fails rather than the example silently going stale.
Patient report: T2DM patients without a statin
Patients aged 40-79 with a Type 2 Diabetes diagnosis and no record of a statin prescription:
<?xml version="1.0" encoding="UTF-8"?>
<patientReport schemaVersion="3">
<name>T2DM Without Statin</name>
<rows>
<patientQuery>
<name>T2DM without statin</name>
<description>Patients aged 40-79 with T2DM and no statin prescription</description>
<population>
<patientQueryReference>ALL</patientQueryReference>
</population>
<codeLists>
<codeList>
<name>T2DM_COD</name>
<codeListDefinition>
<rules>
<inclusion><< 44054006 |Diabetes mellitus type 2|</inclusion>
<exclusion><< 46635009 |Diabetes mellitus type 1|</exclusion>
</rules>
</codeListDefinition>
</codeList>
<codeList>
<name>STATIN_COD</name>
<fixedCodeList>
<codes>
<code>375864007</code>
<code>373541007</code>
<code>108600003</code>
</codes>
</fixedCodeList>
</codeList>
</codeLists>
<variables>
<codedEntryVariable name="T2DM_DAT" code="T2DM_COD" select="clinically_relevant_date" order="clinically_relevant_date" take="latest"/>
<codedEntryVariable name="STATIN_DAT" code="STATIN_COD" select="clinically_relevant_date" order="clinically_relevant_date" take="latest"/>
<patientAge name="AGE" atDate="today"/>
</variables>
<rules>
<rule>
<if>T2DM_DAT NOTEQ NULL AND AGE GTEQ 40 AND AGE LTEQ 79</if>
<then>next-rule</then>
<else>reject</else>
</rule>
<rule>
<if>STATIN_DAT EQ NULL</if>
<then>select</then>
<else>reject</else>
</rule>
</rules>
</patientQuery>
</rows>
<columns>
<demographicInformationColumn value="patientName" headerName="Patient"/>
<demographicInformationColumn value="patientNhsNumber" headerName="NHS Number"/>
<demographicInformationColumn value="patientAge" headerName="Age"/>
<demographicInformationColumn value="namedGP"/>
</columns>
</patientReport>
Activity report: flu vaccinations
Every flu vaccination activity across all patients:
<?xml version="1.0" encoding="UTF-8"?>
<activityReport schemaVersion="3">
<name>Flu Vaccinations</name>
<rows>
<patientQuery>
<name>Flu vaccination activity</name>
<description>Every flu vaccination activity across all patients</description>
<population>
<patientQueryReference>ALL</patientQueryReference>
</population>
<codeLists>
<codeList>
<name>CRITERION_1_COD</name>
<codeListDefinition>
<rules>
<inclusion><< 86198006 |Influenza vaccination|</inclusion>
</rules>
</codeListDefinition>
</codeList>
</codeLists>
<variables>
<codedEntryVariable name="CRITERION_1_DAT" code="CRITERION_1_COD" select="clinically_relevant_date" order="clinically_relevant_date" take="all"/>
</variables>
<rules>
<rule>
<if>TRUE EQ TRUE</if>
<then>select</then>
<else>reject</else>
</rule>
</rules>
</patientQuery>
</rows>
<columns>
<demographicInformationColumn value="patientName"/>
<codedEntryValueColumn value="activityDate"/>
<codedEntryValueColumn value="activity"/>
<codedEntryValueColumn headerName="Activity Description" value="codeDescription"/>
</columns>
</activityReport>
Activity report: diabetes activity (nested population)
Annual review and HbA1c activity, restricted to patients already confirmed to have a diabetes diagnosis via a nested inline patient query in population:
<?xml version="1.0" encoding="UTF-8"?>
<activityReport schemaVersion="3">
<name>Diabetes Activity</name>
<rows>
<patientQuery>
<name>Diabetes annual review activity</name>
<description>Annual review and HbA1c activity for patients with a diabetes diagnosis</description>
<population>
<patientQuery>
<name>Diabetes cohort</name>
<description>Patients with a diabetes diagnosis</description>
<population>
<patientQueryReference>ALL</patientQueryReference>
</population>
<codeLists>
<codeList>
<name>DM_COD</name>
<codeListDefinition>
<rules>
<inclusion><< 73211009 |Diabetes mellitus|</inclusion>
</rules>
</codeListDefinition>
</codeList>
</codeLists>
<variables>
<codedEntryVariable name="DM_DAT" code="DM_COD" select="clinically_relevant_date" order="clinically_relevant_date" take="latest"/>
</variables>
<rules>
<rule>
<if>DM_DAT NOTEQ NULL</if>
<then>select</then>
<else>reject</else>
</rule>
</rules>
</patientQuery>
</population>
<codeLists>
<codeList>
<name>CRITERION_1_COD</name>
<codeListDefinition>
<rules>
<inclusion><< 314764000 |Diabetes mellitus annual review|</inclusion>
</rules>
</codeListDefinition>
</codeList>
<codeList>
<name>CRITERION_2_COD</name>
<codeListDefinition>
<rules>
<inclusion><< 43396009 |Haemoglobin A1c measurement|</inclusion>
</rules>
</codeListDefinition>
</codeList>
</codeLists>
<variables>
<codedEntryVariable name="CRITERION_1_DAT" code="CRITERION_1_COD" select="clinically_relevant_date" order="clinically_relevant_date" take="all"/>
<codedEntryVariable name="CRITERION_2_DAT" code="CRITERION_2_COD" select="clinically_relevant_date" order="clinically_relevant_date" take="all"/>
</variables>
<rules>
<rule>
<if>TRUE EQ TRUE</if>
<then>select</then>
<else>reject</else>
</rule>
</rules>
</patientQuery>
</rows>
<columns>
<demographicInformationColumn value="patientName"/>
<demographicInformationColumn value="patientNhsNumber"/>
<demographicInformationColumn value="patientDateOfBirth"/>
<codedEntryValueColumn value="activityDate"/>
<codedEntryValueColumn value="activity"/>
<codedEntryValueColumn headerName="Activity Description" value="codeDescription"/>
</columns>
</activityReport>
Schema
Common schema (custom-report-common.xsd)
All five report types share this common schema. Each report-type XSD includes it and adds only its own root element.
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<!-- Column value vocabularies, shared with the shipped report definitions. -->
<xs:include schemaLocation="../../../../Infrastructure/Report/XMLSchemas/report-value-vocabularies.xsd"/>
<!--
Compound practitioner identifier list: one or more type:value pairs separated by commas.
type: a lowercase-hyphenated identifier type (e.g. medicus-staff-id, gb-gmc-number)
value: the identifier value (no commas or whitespace)
Example: medicus-staff-id:abc-123,gb-gmc-number:7057629
-->
<xs:simpleType name="compoundPractitionerType">
<xs:restriction base="xs:string">
<xs:pattern value="[a-z][a-z0-9-]*:[^,\s]+(,[a-z][a-z0-9-]*:[^,\s]+)*"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="nonEmptyTrimmedString">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="reportNameType">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="255"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="reportIdentifierType">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="runtimeValueNameType">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="yyyymmddType">
<xs:restriction base="xs:string">
<xs:pattern value="\d{8}"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="runtimeValueType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="name" type="runtimeValueNameType" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="patientQueryRuntimeValueType">
<xs:sequence>
<xs:element name="runtimeValue" type="runtimeValueType" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="name" type="runtimeValueNameType" use="required"/>
</xs:complexType>
<xs:complexType name="patientQueryRuntimeValuesType">
<xs:sequence>
<xs:element name="patientQueryRuntimeValue" type="patientQueryRuntimeValueType" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="demographicInformationColumnType">
<xs:attribute name="value" type="demographicInformationValueType" use="required"/>
<xs:attribute name="headerName" type="xs:string" use="optional"/>
<xs:attribute name="useAsFilter" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="codedEntryValueColumnType">
<xs:attribute name="value" type="codedEntryValueType" use="required"/>
<xs:attribute name="headerName" type="xs:string" use="optional"/>
<xs:attribute name="useAsFilter" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="anyElementType" mixed="true">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" processContents="skip"/>
</xs:sequence>
<xs:anyAttribute processContents="skip"/>
</xs:complexType>
<xs:complexType name="dateColumnType" mixed="true">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" processContents="skip"/>
</xs:sequence>
<xs:attribute name="headerName" type="xs:string" use="optional"/>
<xs:attribute name="useAsFilter" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="derivedValueColumnType" mixed="true">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" processContents="skip"/>
</xs:sequence>
<xs:attribute name="headerName" type="xs:string" use="optional"/>
<xs:attribute name="useAsFilter" type="xs:string" use="optional"/>
<xs:attribute name="basePatientQuery" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="valueColumnType" mixed="true">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" processContents="skip"/>
</xs:sequence>
<xs:attribute name="headerName" type="xs:string" use="optional"/>
<xs:attribute name="useAsFilter" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="prescriptionValueColumnType">
<xs:attribute name="value" type="prescriptionValueType" use="required"/>
<xs:attribute name="headerName" type="xs:string" use="optional"/>
<xs:attribute name="useAsFilter" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="prescriptionIssueValueColumnType">
<xs:attribute name="value" type="prescriptionIssueValueType" use="required"/>
<xs:attribute name="headerName" type="xs:string" use="optional"/>
<xs:attribute name="useAsFilter" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="appointmentValueColumnType">
<xs:attribute name="value" type="appointmentValueType" use="required"/>
<xs:attribute name="headerName" type="xs:string" use="optional"/>
<xs:attribute name="useAsFilter" type="xs:string" use="optional"/>
</xs:complexType>
<!-- Columns read straight off the patient or a retained record, with no patient query of their
own to run. Shared with a dashboard tab's <drilldownColumns>, which allows only these. -->
<xs:group name="directColumnGroup">
<xs:choice>
<xs:element name="demographicInformationColumn" type="demographicInformationColumnType"/>
<xs:element name="codedEntryValueColumn" type="codedEntryValueColumnType"/>
<xs:element name="prescriptionValueColumn" type="prescriptionValueColumnType"/>
<xs:element name="prescriptionIssueValueColumn" type="prescriptionIssueValueColumnType"/>
<xs:element name="appointmentValueColumn" type="appointmentValueColumnType"/>
</xs:choice>
</xs:group>
<xs:complexType name="columnsType">
<xs:choice minOccurs="1" maxOccurs="unbounded">
<xs:group ref="directColumnGroup"/>
<xs:element name="dateColumn" type="dateColumnType"/>
<xs:element name="derivedValueColumn" type="derivedValueColumnType"/>
<xs:element name="valueColumn" type="valueColumnType"/>
</xs:choice>
</xs:complexType>
<!--
Export-only column names, for a CSV whose headers are a contract with something downstream
rather than a label on a screen. field= is the column's fieldName, which for a split column
is its dotted path and for an organisation column is the name the merger gives it. Neither
of those has a column element of its own to carry an attribute. A field naming no column the
export produces is rejected at export time, once the column list is finally known.
-->
<xs:complexType name="exportColumnsType">
<xs:choice maxOccurs="unbounded">
<xs:element name="column">
<xs:complexType>
<xs:attribute name="field" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="headerName" type="nonEmptyTrimmedString" use="required"/>
</xs:complexType>
</xs:element>
<!--
A column the export appends itself, holding the same value on every row. Its own element
rather than a column without a field=, because XSD 1.0 cannot require exactly one of two
attributes and the shape would only be checked once the export ran.
-->
<xs:element name="generatedColumn">
<xs:complexType>
<xs:attribute name="value" type="generatedExportColumnValueType" use="required"/>
<xs:attribute name="headerName" type="nonEmptyTrimmedString" use="required"/>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
<!-- Kept closed rather than taking a date format, so a typo cannot reach a file unnoticed. -->
<xs:simpleType name="generatedExportColumnValueType">
<xs:restriction base="xs:string">
<xs:enumeration value="currentTimestamp"/>
</xs:restriction>
</xs:simpleType>
<!-- rowsType: exactly one <patientQuery> -->
<xs:complexType name="rowsType">
<xs:sequence>
<xs:element name="patientQuery" type="inlinePatientQueryType"/>
</xs:sequence>
</xs:complexType>
<!-- inlinePatientQueryType: full query (no flat definedPatientIds form, use patientIdList inside population instead) -->
<xs:complexType name="inlinePatientQueryType">
<xs:sequence>
<!-- required: PatientQueryDefinition::validateXmlElementsRequirements() throws
"Name not found" / "Description not found" at runtime when either is absent. -->
<xs:element name="name" type="xs:string"/>
<xs:element name="description" type="xs:string"/>
<xs:element name="population" type="populationType"/>
<xs:element name="codeLists" type="codeListsType"/>
<xs:element name="variables" type="variablesType"/>
<xs:element name="rules" type="rulesType"/>
</xs:sequence>
</xs:complexType>
<!-- populationType: one of four population sources -->
<xs:complexType name="populationType">
<xs:choice>
<xs:element name="patientQueryReference" type="nonEmptyTrimmedString"/>
<xs:element name="patientQuery" type="embeddedPatientQueryType"/>
<xs:element name="patientReportReference" type="nonEmptyTrimmedString"/>
<xs:element name="patientIdList" type="patientIdListType"/>
</xs:choice>
</xs:complexType>
<!-- embeddedPatientQueryType: recursive inline patient query (no schemaVersion attribute) -->
<xs:complexType name="embeddedPatientQueryType">
<xs:sequence>
<!-- required: see inlinePatientQueryType above, same runtime check applies to embedded/population sub-queries. -->
<xs:element name="name" type="xs:string"/>
<xs:element name="description" type="xs:string"/>
<xs:element name="population" type="populationType"/>
<xs:element name="codeLists" type="codeListsType"/>
<xs:element name="variables" type="variablesType"/>
<xs:element name="rules" type="rulesType"/>
</xs:sequence>
</xs:complexType>
<!-- patientIdListType: one or more patient UUIDs -->
<xs:complexType name="patientIdListType">
<xs:sequence>
<xs:element name="patientId" type="nonEmptyTrimmedString" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<!-- variablesType: zero or more variable declarations -->
<xs:complexType name="variablesType">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="fixedDate" type="fixedDateVariableType"/>
<xs:element name="codedEntryVariable" type="codedEntryDateVariableType"/>
<xs:element name="derivedFixedDate" type="derivedFixedDateType"/>
<xs:element name="patientAge" type="patientAgeVariableType"/>
<xs:element name="patientEntryDate" type="patientEntryDateVariableType"/>
<xs:element name="patientClinicalSex" type="namedVariableType"/>
<xs:element name="patientRegistrationVariable" type="patientRegistrationVariableType"/>
<xs:element name="clinicalCaseVariable" type="clinicalCaseVariableType"/>
<xs:element name="patientNamedGp" type="namedVariableType"/>
<xs:element name="patientRegisteredGpPractice" type="namedVariableType"/>
<xs:element name="patientRegisteredGpPracticeName" type="namedVariableType"/>
<xs:element name="patientQuery" type="patientQueryVariableType"/>
<xs:element name="prescriptionVariable" type="prescriptionVariableType"/>
<xs:element name="prescriptionIssueVariable" type="prescriptionIssueVariableType"/>
<xs:element name="appointmentVariable" type="appointmentVariableType"/>
<xs:element name="codedEntryDate" type="lenientVariableType"/>
<xs:element name="codedEntryValue" type="lenientVariableType"/>
<xs:element name="codedEntryFromListVariable" type="lenientVariableType"/>
<xs:element name="codedEntryValueFromList" type="lenientVariableType"/>
<xs:element name="entryDateFromList" type="lenientVariableType"/>
<xs:element name="aggregatedEntryDate" type="lenientVariableType"/>
<xs:element name="conditionalCodedEntryVariable" type="lenientVariableType"/>
<xs:element name="derivedCodedEntryDate" type="lenientVariableType"/>
<xs:element name="derivedCodedEntryPractitioner" type="lenientVariableType"/>
<xs:element name="derivedEntryDateFromList" type="lenientVariableType"/>
<xs:element name="derivedPatientEntryDate" type="lenientVariableType"/>
<xs:element name="derivedPrescriptionIssueDate" type="lenientVariableType"/>
<xs:element name="derivedPrescriptionIssueDescription" type="lenientVariableType"/>
<xs:element name="derivedPrescriptionDate" type="lenientVariableType"/>
<xs:element name="derivedBooleanValue" type="lenientVariableType"/>
<xs:element name="patientCareHome" type="lenientVariableType"/>
<xs:element name="patientEthnic" type="lenientVariableType"/>
<xs:element name="patientRegistrationType" type="lenientVariableType"/>
<xs:element name="specialCase" type="lenientVariableType"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="lenientVariableType" mixed="true">
<xs:sequence>
<xs:any processContents="skip" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:anyAttribute processContents="skip"/>
</xs:complexType>
<!-- namedVariableType: attribute-only variable element -->
<xs:complexType name="namedVariableType">
<xs:anyAttribute processContents="skip"/>
</xs:complexType>
<!--
patientAgeVariableType: unlike the other namedVariableType uses, PatientQueryPatientAge
throws a runtime \DomainException ("… is missing atDate attribute") when atDate is absent,
so, unlike patientClinicalSex/patientNamedGp, this one needs its attributes enumerated.
-->
<xs:complexType name="patientAgeVariableType">
<xs:attribute name="name" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="description" type="xs:string" use="optional"/>
<xs:attribute name="atDate" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="type" type="xs:string" use="optional"/>
</xs:complexType>
<!-- patientQueryVariableType: references another patient query as a variable -->
<xs:complexType name="patientQueryVariableType">
<xs:attribute name="name" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="description" type="xs:string" use="optional"/>
<xs:attribute name="queryIdentifier" type="nonEmptyTrimmedString" use="required"/>
</xs:complexType>
<xs:complexType name="fixedDateVariableType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="name" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="description" type="xs:string" use="optional"/>
<xs:attribute name="runtimeValue" type="xs:string" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<!-- Mirrors CodedEntryColumn; ReportValueVocabularyParityTest keeps the two in step. -->
<xs:simpleType name="codedEntryColumnType">
<xs:restriction base="xs:string">
<xs:enumeration value="uid"/>
<xs:enumeration value="care_record_id"/>
<xs:enumeration value="patient_id"/>
<xs:enumeration value="entity_type"/>
<xs:enumeration value="code"/>
<xs:enumeration value="code_description"/>
<xs:enumeration value="clinically_relevant_date"/>
<xs:enumeration value="clinically_relevant_time"/>
<xs:enumeration value="care_record_sorting_date_time"/>
<xs:enumeration value="created_in_original_system_date_time"/>
<xs:enumeration value="created_date_time"/>
<xs:enumeration value="end_date"/>
<xs:enumeration value="status"/>
<xs:enumeration value="encounter_id"/>
<xs:enumeration value="value_1"/>
<xs:enumeration value="value_1_unit_code"/>
<xs:enumeration value="value_1_unit_description"/>
<xs:enumeration value="value_2"/>
<xs:enumeration value="value_2_unit_code"/>
<xs:enumeration value="value_2_unit_description"/>
<xs:enumeration value="body_site_code"/>
<xs:enumeration value="body_site_description"/>
<xs:enumeration value="route_code"/>
<xs:enumeration value="route_description"/>
<xs:enumeration value="batch_number"/>
<xs:enumeration value="expiry_date"/>
<xs:enumeration value="practitioner_name"/>
<xs:enumeration value="practitioner_identifier_type"/>
<xs:enumeration value="practitioner_identifier_value"/>
<xs:enumeration value="organisation_identifier_type"/>
<xs:enumeration value="organisation_identifier_value"/>
<xs:enumeration value="performed_at_local_organisation"/>
<xs:enumeration value="organisation_name"/>
<xs:enumeration value="encounter_type_code"/>
<xs:enumeration value="encounter_type_description"/>
</xs:restriction>
</xs:simpleType>
<!-- Comma-separated list of codedEntryColumnType tokens, as extractSelectColumns() splits it. -->
<xs:simpleType name="codedEntryColumnListType">
<xs:restriction base="xs:string">
<xs:pattern value="\s*(uid|care_record_id|patient_id|entity_type|code|code_description|clinically_relevant_date|clinically_relevant_time|care_record_sorting_date_time|created_in_original_system_date_time|created_date_time|end_date|status|encounter_id|value_1|value_1_unit_code|value_1_unit_description|value_2|value_2_unit_code|value_2_unit_description|body_site_code|body_site_description|route_code|route_description|batch_number|expiry_date|practitioner_name|practitioner_identifier_type|practitioner_identifier_value|organisation_identifier_type|organisation_identifier_value|performed_at_local_organisation|organisation_name|encounter_type_code|encounter_type_description)\s*(,\s*(uid|care_record_id|patient_id|entity_type|code|code_description|clinically_relevant_date|clinically_relevant_time|care_record_sorting_date_time|created_in_original_system_date_time|created_date_time|end_date|status|encounter_id|value_1|value_1_unit_code|value_1_unit_description|value_2|value_2_unit_code|value_2_unit_description|body_site_code|body_site_description|route_code|route_description|batch_number|expiry_date|practitioner_name|practitioner_identifier_type|practitioner_identifier_value|organisation_identifier_type|organisation_identifier_value|performed_at_local_organisation|organisation_name|encounter_type_code|encounter_type_description)\s*)*"/>
</xs:restriction>
</xs:simpleType>
<!-- Mirrors PrescriptionColumn; ReportValueVocabularyParityTest keeps the two in step. -->
<xs:simpleType name="prescriptionColumnType">
<xs:restriction base="xs:string">
<xs:enumeration value="uid"/>
<xs:enumeration value="patient_id"/>
<xs:enumeration value="type"/>
<xs:enumeration value="status"/>
<xs:enumeration value="product_code"/>
<xs:enumeration value="product_description"/>
<xs:enumeration value="dosage_instruction"/>
<xs:enumeration value="start_date"/>
<xs:enumeration value="expected_end_date"/>
<xs:enumeration value="authorised_date"/>
<xs:enumeration value="authorised_by_practitioner_identifier_type"/>
<xs:enumeration value="authorised_by_practitioner_identifier_value"/>
<xs:enumeration value="authorised_by_practitioner_name"/>
<xs:enumeration value="last_issue_date"/>
<xs:enumeration value="discontinued_date"/>
</xs:restriction>
</xs:simpleType>
<!-- Comma-separated list of prescriptionColumnType tokens, as extractSelectColumns() splits it. -->
<xs:simpleType name="prescriptionColumnListType">
<xs:restriction base="xs:string">
<xs:pattern value="\s*(uid|patient_id|type|status|product_code|product_description|dosage_instruction|start_date|expected_end_date|authorised_date|authorised_by_practitioner_identifier_type|authorised_by_practitioner_identifier_value|authorised_by_practitioner_name|last_issue_date|discontinued_date)\s*(,\s*(uid|patient_id|type|status|product_code|product_description|dosage_instruction|start_date|expected_end_date|authorised_date|authorised_by_practitioner_identifier_type|authorised_by_practitioner_identifier_value|authorised_by_practitioner_name|last_issue_date|discontinued_date)\s*)*"/>
</xs:restriction>
</xs:simpleType>
<!-- Mirrors PrescriptionIssueColumn; ReportValueVocabularyParityTest keeps the two in step. -->
<xs:simpleType name="prescriptionIssueColumnType">
<xs:restriction base="xs:string">
<xs:enumeration value="id"/>
<xs:enumeration value="patient_id"/>
<xs:enumeration value="prescription_id"/>
<xs:enumeration value="product_code"/>
<xs:enumeration value="product_description"/>
<xs:enumeration value="expected_days_supply"/>
<xs:enumeration value="supply_end_date"/>
<xs:enumeration value="quantity_value"/>
<xs:enumeration value="quantity_unit_code"/>
<xs:enumeration value="quantity_unit_description"/>
<xs:enumeration value="issue_date"/>
<xs:enumeration value="issue_method"/>
<xs:enumeration value="issued_by_practitioner_identifier_type"/>
<xs:enumeration value="issued_by_practitioner_identifier_value"/>
<xs:enumeration value="issued_by_practitioner_name"/>
<xs:enumeration value="issued_by_organisation"/>
<xs:enumeration value="issue_number"/>
<xs:enumeration value="issue_status"/>
<xs:enumeration value="pharmacy_ods_code"/>
<xs:enumeration value="course_dosage_instruction"/>
<xs:enumeration value="course_type"/>
</xs:restriction>
</xs:simpleType>
<!-- Comma-separated list of prescriptionIssueColumnType tokens, as extractSelectColumns() splits it. -->
<xs:simpleType name="prescriptionIssueColumnListType">
<xs:restriction base="xs:string">
<xs:pattern value="\s*(id|patient_id|prescription_id|product_code|product_description|expected_days_supply|supply_end_date|quantity_value|quantity_unit_code|quantity_unit_description|issue_date|issue_method|issued_by_practitioner_identifier_type|issued_by_practitioner_identifier_value|issued_by_practitioner_name|issued_by_organisation|issue_number|issue_status|pharmacy_ods_code|course_dosage_instruction|course_type)\s*(,\s*(id|patient_id|prescription_id|product_code|product_description|expected_days_supply|supply_end_date|quantity_value|quantity_unit_code|quantity_unit_description|issue_date|issue_method|issued_by_practitioner_identifier_type|issued_by_practitioner_identifier_value|issued_by_practitioner_name|issued_by_organisation|issue_number|issue_status|pharmacy_ods_code|course_dosage_instruction|course_type)\s*)*"/>
</xs:restriction>
</xs:simpleType>
<!-- Mirrors AppointmentColumn; ReportValueVocabularyParityTest keeps the two in step. -->
<xs:simpleType name="appointmentColumnType">
<xs:restriction base="xs:string">
<xs:enumeration value="id"/>
<xs:enumeration value="patient_id"/>
<xs:enumeration value="status"/>
<xs:enumeration value="practitioner_identifier_type"/>
<xs:enumeration value="practitioner_identifier_value"/>
<xs:enumeration value="practitioner_name"/>
<xs:enumeration value="appointment_type_id"/>
<xs:enumeration value="appointment_type_name"/>
<xs:enumeration value="appointment_service_id"/>
<xs:enumeration value="appointment_service_name"/>
<xs:enumeration value="nhs_national_slot_type_category"/>
<xs:enumeration value="intended_start_date_time"/>
<xs:enumeration value="intended_end_date_time"/>
<xs:enumeration value="intended_duration_seconds"/>
<xs:enumeration value="appointment_container_id"/>
<xs:enumeration value="delivery_mode"/>
<xs:enumeration value="linked_encounter_id"/>
<xs:enumeration value="arrived_date_time"/>
<xs:enumeration value="booked_date_time"/>
<xs:enumeration value="cancelled_date_time"/>
<xs:enumeration value="rescheduled_date_time"/>
<xs:enumeration value="appointment_site_id"/>
<xs:enumeration value="appointment_room_id"/>
<xs:enumeration value="appointment_site_name"/>
<xs:enumeration value="appointment_room_name"/>
</xs:restriction>
</xs:simpleType>
<!-- Comma-separated list of appointmentColumnType tokens, as extractSelectColumns() splits it. -->
<xs:simpleType name="appointmentColumnListType">
<xs:restriction base="xs:string">
<xs:pattern value="\s*(id|patient_id|status|practitioner_identifier_type|practitioner_identifier_value|practitioner_name|appointment_type_id|appointment_type_name|appointment_service_id|appointment_service_name|nhs_national_slot_type_category|intended_start_date_time|intended_end_date_time|intended_duration_seconds|appointment_container_id|delivery_mode|linked_encounter_id|arrived_date_time|booked_date_time|cancelled_date_time|rescheduled_date_time|appointment_site_id|appointment_room_id|appointment_site_name|appointment_room_name)\s*(,\s*(id|patient_id|status|practitioner_identifier_type|practitioner_identifier_value|practitioner_name|appointment_type_id|appointment_type_name|appointment_service_id|appointment_service_name|nhs_national_slot_type_category|intended_start_date_time|intended_end_date_time|intended_duration_seconds|appointment_container_id|delivery_mode|linked_encounter_id|arrived_date_time|booked_date_time|cancelled_date_time|rescheduled_date_time|appointment_site_id|appointment_room_id|appointment_site_name|appointment_room_name)\s*)*"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="codedEntryDateVariableType">
<xs:attribute name="name" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="description" type="xs:string" use="optional"/>
<xs:attribute name="code" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="select" type="codedEntryColumnListType" use="required"/>
<xs:attribute name="order" type="codedEntryColumnType" use="required"/>
<xs:attribute name="take" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="latest"/>
<xs:enumeration value="earliest"/>
<xs:enumeration value="all"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="value1" type="xs:string" use="optional"/>
<xs:attribute name="value2" type="xs:string" use="optional"/>
<xs:attribute name="performedAtLocalOrganisation" type="xs:string" use="optional"/>
<xs:attribute name="clinicallyRelevantDate" type="xs:string" use="optional"/>
<xs:attribute name="encounterTypeCode" type="xs:string" use="optional"/>
<!-- Optional cap for take="latest"/"earliest": take the top N records per patient. -->
<xs:attribute name="limit" type="xs:positiveInteger" use="optional"/>
</xs:complexType>
<xs:complexType name="prescriptionVariableType">
<xs:attribute name="name" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="description" type="xs:string" use="optional"/>
<xs:attribute name="productCode" type="nonEmptyTrimmedString" use="optional"/>
<xs:attribute name="excludeProductCode" type="nonEmptyTrimmedString" use="optional"/>
<xs:attribute name="lastIssueFields" type="prescriptionIssueColumnListType" use="optional"/>
<xs:attribute name="select" type="prescriptionColumnListType" use="required"/>
<xs:attribute name="order" type="prescriptionColumnType" use="required"/>
<xs:attribute name="take" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="latest"/>
<xs:enumeration value="earliest"/>
<xs:enumeration value="all"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="type" type="xs:string" use="optional"/>
<xs:attribute name="endDate" type="xs:string" use="optional"/>
<xs:attribute name="status" type="xs:string" use="optional"/>
<xs:attribute name="lastIssueDate" type="xs:string" use="optional"/>
<xs:attribute name="startDate" type="xs:string" use="optional"/>
<xs:attribute name="discontinuedDate" type="xs:string" use="optional"/>
<xs:attribute name="authorisingPractitioner" type="compoundPractitionerType" use="optional"/>
</xs:complexType>
<xs:complexType name="prescriptionIssueVariableType">
<xs:attribute name="name" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="description" type="xs:string" use="optional"/>
<xs:attribute name="productCode" type="nonEmptyTrimmedString" use="optional"/>
<xs:attribute name="excludeProductCode" type="nonEmptyTrimmedString" use="optional"/>
<xs:attribute name="select" type="prescriptionIssueColumnListType" use="required"/>
<xs:attribute name="order" type="prescriptionIssueColumnType" use="required"/>
<xs:attribute name="take" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="latest"/>
<xs:enumeration value="earliest"/>
<xs:enumeration value="all"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="issueDate" type="xs:string" use="optional"/>
<xs:attribute name="issueMethod" type="xs:string" use="optional"/>
<xs:attribute name="quantityValue" type="xs:string" use="optional"/>
<xs:attribute name="quantityUnitCode" type="xs:string" use="optional"/>
<xs:attribute name="supplyEndDate" type="xs:string" use="optional"/>
<xs:attribute name="issueStatus" type="xs:string" use="optional"/>
<xs:attribute name="issuedByPractitioner" type="compoundPractitionerType" use="optional"/>
<xs:attribute name="pharmacyOdsCode" type="xs:string" use="optional"/>
<!-- Optional cap for take="latest"/"earliest": take the top N records per patient. -->
<xs:attribute name="limit" type="xs:positiveInteger" use="optional"/>
</xs:complexType>
<xs:complexType name="appointmentVariableType">
<xs:attribute name="name" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="description" type="xs:string" use="optional"/>
<xs:attribute name="select" type="appointmentColumnListType" use="required"/>
<xs:attribute name="order" type="appointmentColumnType" use="required"/>
<xs:attribute name="take" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="latest"/>
<xs:enumeration value="earliest"/>
<xs:enumeration value="all"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="status" type="xs:string" use="optional"/>
<xs:attribute name="appointmentType" type="xs:string" use="optional"/>
<xs:attribute name="appointmentService" type="xs:string" use="optional"/>
<xs:attribute name="deliveryMode" type="xs:string" use="optional"/>
<xs:attribute name="intendedStartDateTime" type="xs:string" use="optional"/>
<xs:attribute name="intendedEndDateTime" type="xs:string" use="optional"/>
<xs:attribute name="bookedDateTime" type="xs:string" use="optional"/>
<xs:attribute name="practitioner" type="compoundPractitionerType" use="optional"/>
<xs:attribute name="nhsNationalSlotTypeCategory" type="xs:string" use="optional"/>
<xs:attribute name="appointmentSite" type="xs:string" use="optional"/>
<xs:attribute name="appointmentRoom" type="xs:string" use="optional"/>
</xs:complexType>
<!--
patientRegistrationVariableType: the v3 source of truth for any registration data
referenced by query rules or demographic registration columns.
Replaces the v2 pairing of <patientRegistrationType> + <patientEntryDate patientField="registration_date|…">.
-->
<xs:complexType name="patientRegistrationVariableType">
<xs:attribute name="name" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="description" type="xs:string" use="optional"/>
<xs:attribute name="select" use="required">
<xs:simpleType>
<!-- Comma-separated list of registration columns. Each token must be one of
id, patient_id, start_date, end_date, registration_type,
prescriptions_dispensed_by_practice, care_home_resident. -->
<xs:restriction base="xs:string">
<xs:pattern value="(\s*(id|patient_id|start_date|end_date|registration_type|prescriptions_dispensed_by_practice|care_home_resident)\s*)(,\s*(id|patient_id|start_date|end_date|registration_type|prescriptions_dispensed_by_practice|care_home_resident)\s*)*"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="order" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="id"/>
<xs:enumeration value="patient_id"/>
<xs:enumeration value="start_date"/>
<xs:enumeration value="end_date"/>
<xs:enumeration value="registration_type"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="take" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="latest"/>
<xs:enumeration value="earliest"/>
<xs:enumeration value="all"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="startDate" type="xs:string" use="optional"/>
<xs:attribute name="endDate" type="xs:string" use="optional"/>
<xs:attribute name="registrationType" type="xs:string" use="optional"/>
<xs:attribute name="prescriptionsDispensedByPractice" use="optional">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="TRUE"/>
<xs:enumeration value="FALSE"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="careHomeResident" use="optional">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="TRUE"/>
<xs:enumeration value="FALSE"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<!-- Mirrors ClinicalCaseColumn; QueryVariableColumnVocabularyParityTest keeps the two in step. -->
<xs:simpleType name="clinicalCaseColumnType">
<xs:restriction base="xs:string">
<xs:enumeration value="patient_id"/>
<xs:enumeration value="case_status"/>
<xs:enumeration value="created_date_time"/>
<xs:enumeration value="closed_date_time"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="clinicalCaseColumnListType">
<xs:restriction base="xs:string">
<xs:pattern value="\s*(patient_id|case_status|created_date_time|closed_date_time)\s*(,\s*(patient_id|case_status|created_date_time|closed_date_time)\s*)*"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="clinicalCaseVariableType">
<xs:attribute name="name" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="description" type="xs:string" use="optional"/>
<xs:attribute name="select" type="clinicalCaseColumnListType" use="required"/>
<xs:attribute name="take" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="latest"/>
<xs:enumeration value="earliest"/>
<xs:enumeration value="all"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="order" type="clinicalCaseColumnType" use="required"/>
<xs:attribute name="status" use="optional">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="\s*(open|closed)\s*(,\s*(open|closed)\s*)*"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="type" type="xs:string" use="optional"/>
<xs:attribute name="commissionedServiceOrganisation" type="xs:string" use="optional"/>
<xs:attribute name="commissionedServiceServiceIdentifier" type="xs:string" use="optional"/>
<xs:attribute name="createdDateTime" type="xs:string" use="optional"/>
<xs:attribute name="closedDateTime" type="xs:string" use="optional"/>
</xs:complexType>
<!--
patientEntryDateVariableType: restricts patientField to the non-registration cases.
Registration-related fields (registration_date, deregistration_date, int_*, temp_*) were removed in v3;
use <patientRegistrationVariable> for those.
-->
<xs:complexType name="patientEntryDateVariableType">
<xs:attribute name="name" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="description" type="xs:string" use="optional"/>
<xs:attribute name="patientField" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="date_of_birth"/>
<xs:enumeration value="death_date"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="logic" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="derivedFixedDateType">
<xs:attribute name="name" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="description" type="xs:string" use="optional"/>
<xs:attribute name="fixedDateName" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="operation" type="nonEmptyTrimmedString" use="required"/>
</xs:complexType>
<!-- codeListsType: zero or more code list entries (each wrapped in a <codeList> element) -->
<xs:complexType name="codeListsType">
<xs:sequence>
<xs:element name="codeList" type="codeListEntryType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<!-- codeListEntryType: wrapper containing <name> and one of three payload types -->
<xs:complexType name="codeListEntryType">
<xs:sequence>
<xs:element name="name" type="nonEmptyTrimmedString"/>
<xs:choice>
<xs:element name="codeListReference" type="nonEmptyTrimmedString"/>
<xs:element name="codeListDefinition" type="codeListDefinitionBodyType"/>
<xs:element name="fixedCodeList" type="fixedCodeListType"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
<xs:complexType name="codeListDefinitionBodyType">
<xs:sequence>
<xs:element name="rules" type="codeListRulesType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="fixedCodeListType">
<xs:sequence>
<xs:element name="codes">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="nonEmptyTrimmedString" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="codeListRulesType">
<xs:choice minOccurs="1" maxOccurs="unbounded">
<xs:element name="inclusion" type="nonEmptyTrimmedString"/>
<xs:element name="exclusion" type="nonEmptyTrimmedString"/>
</xs:choice>
</xs:complexType>
<!-- rulesType / ruleType: one or more if/then/else rules -->
<xs:complexType name="rulesType">
<xs:sequence>
<xs:element name="rule" type="ruleType" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ruleType">
<xs:sequence>
<xs:element name="description" type="xs:string" minOccurs="0"/>
<xs:element name="if" type="ruleExpressionType"/>
<xs:element name="then" type="thenValueType"/>
<xs:element name="else" type="elseValueType"/>
</xs:sequence>
</xs:complexType>
<!-- ruleExpressionType: must not end with whitespace -->
<xs:simpleType name="ruleExpressionType">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:pattern value=".*\S"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="thenValueType">
<xs:restriction base="xs:string">
<xs:enumeration value="next-rule"/>
<xs:enumeration value="select"/>
<xs:enumeration value="reject"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="elseValueType">
<xs:restriction base="xs:string">
<xs:enumeration value="next-rule"/>
<xs:enumeration value="select"/>
<xs:enumeration value="reject"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
Patient report (custom-patient-report.xsd)
<?xml version="1.0" encoding="UTF-8"?>
<!--
XSD for v3 patient reports validation
==== Example Schema =====
<patientReport schemaVersion="3">
<name>…</name>
<rows>
<patientQuery>
<population>
<patientQueryReference>ALL</patientQueryReference>
OR
<patientQueryReference>nhs-england/GMS_REG</patientQueryReference>
OR
<patientQuery> <- another full inline query
<population>…</population>
<codeLists>…</codeLists>
<variables>…</variables>
<rules>…</rules>
</patientQuery>
OR
<patientReportReference>report-uuid</patientReportReference>
OR
<patientIdList>
<patientId>uuid-1</patientId>
<patientId>uuid-2</patientId>
</patientIdList>
</population>
<codeLists>
<codeList>
<name>MY_COD</name>
<codeListReference>nhs-england/MY_COD</codeListReference>
</codeList>
<codeList>
<name>ECL_COD</name>
<codeListDefinition>
<rules>
<inclusion><< 73211009 |Diabetes mellitus|</inclusion>
</rules>
</codeListDefinition>
</codeList>
</codeLists>
<variables>…</variables>
<rules>…</rules>
</patientQuery>
</rows>
<columns>…</columns>
</patientReport>
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:include schemaLocation="custom-report-common.xsd"/>
<!--Verify root of report has required tags/attributes -->
<xs:element name="patientReport">
<xs:complexType>
<xs:sequence>
<!-- reportNameType caps at 255 chars to match the custom_report.name DB column. -->
<xs:element name="name" type="reportNameType"/>
<xs:element name="rows" type="rowsType"/>
<xs:element name="columns" type="columnsType"/>
<xs:element name="exportColumns" type="exportColumnsType" minOccurs="0"/>
</xs:sequence>
<!--
Which of the two patient-shaped focuses built this. Both produce a patientReport
with the same columns and the same population, so nothing in the definition
distinguishes them and reopening one had to guess. Optional, because every report
saved before this existed has no stamp and still has to open.
-->
<xs:attribute name="focus" use="optional">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="patients"/>
<xs:enumeration value="identifiers"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="schemaVersion" use="required">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:enumeration value="3"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>
Appointment report (custom-appointment-report.xsd)
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:include schemaLocation="custom-report-common.xsd"/>
<xs:element name="appointmentReport">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="reportNameType"/>
<xs:element name="rows" type="rowsType"/>
<xs:element name="columns" type="columnsType"/>
<xs:element name="exportColumns" type="exportColumnsType" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="schemaVersion" use="required">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:enumeration value="3"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>
Prescription report (custom-prescription-report.xsd)
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:include schemaLocation="custom-report-common.xsd"/>
<xs:element name="prescriptionReport">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="reportNameType"/>
<xs:element name="rows" type="rowsType"/>
<xs:element name="columns" type="columnsType"/>
<xs:element name="exportColumns" type="exportColumnsType" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="schemaVersion" use="required">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:enumeration value="3"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>
Prescription issue report (custom-prescription-issue-report.xsd)
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:include schemaLocation="custom-report-common.xsd"/>
<xs:element name="prescriptionIssueReport">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="reportNameType"/>
<xs:element name="rows" type="rowsType"/>
<xs:element name="columns" type="columnsType"/>
<xs:element name="exportColumns" type="exportColumnsType" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="schemaVersion" use="required">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:enumeration value="3"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>
Activity report (custom-activity-report.xsd)
<?xml version="1.0" encoding="UTF-8"?>
<!--
XSD for v3 activity reports validation
Example:
<activityReport schemaVersion="3">
<name>My Activity Report</name>
<rows>
<patientQuery>
<name>My Activity Report</name>
<description>My Activity Report</description>
<population>
<patientQuery>
<name>My Activity Report-population</name>
<description>My Activity Report-population</description>
<population>
<patientQueryReference>ALL</patientQueryReference>
</population>
<codeLists/>
<variables>...</variables>
<rules>...</rules>
</patientQuery>
</population>
<codeLists>
<codeList>
<name>CRITERIA_1_COD</name>
<codeListDefinition>
<rules>
<inclusion><< 73211009 |Diabetes mellitus|</inclusion>
</rules>
</codeListDefinition>
</codeList>
</codeLists>
<variables>
<codedEntryVariable name="CRITERIA_1_DAT" code="CRITERIA_1_COD"
select="clinically_relevant_date"
order="clinically_relevant_date"
take="all"/>
</variables>
<rules>
<rule>
<if>TRUE EQ TRUE</if>
<then>select</then>
<else>reject</else></rule>
</rules>
</patientQuery>
</rows>
<columns>
<demographicInformationColumn value="patientName"/>
<demographicInformationColumn value="activityDate"/>
<demographicInformationColumn value="activity"/>
</columns>
</activityReport>
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:include schemaLocation="custom-report-common.xsd"/>
<xs:element name="activityReport">
<xs:complexType>
<xs:sequence>
<!-- reportNameType caps at 255 chars to match the custom_report.name DB column. -->
<xs:element name="name" type="reportNameType"/>
<xs:element name="rows" type="rowsType"/>
<xs:element name="columns" type="columnsType"/>
<xs:element name="exportColumns" type="exportColumnsType" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="schemaVersion" use="required">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:enumeration value="3"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>
Export columns
An optional <exportColumns> sibling of <columns> names the headers a CSV
export of the report uses, as a contract with whatever consumes the file
downstream, independent of the on-screen column headers.
<exportColumns>
<column field="patientNhsNumber" headerName="NHS_NUMBER"/>
<generatedColumn value="currentTimestamp" headerName="EXPORT_DATE"/>
</exportColumns>
| Element | Attributes | Description |
|---|---|---|
column | field (required), headerName (required) | Renames an existing column's CSV header. field is that column's field name: a plain report column's field, a split column's dotted path, or the name an organisation column's merger gives it. |
generatedColumn | value (required, only currentTimestamp), headerName (required) | Appends a column the export generates itself, holding the same value on every row. |
field is checked against the report's actual column list at export time, not
at publish time, so a field that doesn't name a real column is only rejected
when the export runs.
Column reference
useAsFilter="true" on any column type below, and on the computed columns
above, makes that column available as a report filter in the report viewer, in
addition to being rendered as a normal column. It defaults to false
everywhere except derivedValueColumn, where it defaults to true.
Computed value columns
Unlike the enumerated column types below, dateColumn, valueColumn, and derivedValueColumn don't take a fixed value attribute. Instead, their content is an inline patient-query-like structure (see Patient Query Language) that computes a value per patient at report run time - useful for showing a derived date, a computed value, or a value from a sub-query that isn't covered by the other column types.
| Element | Attributes | Description |
|---|---|---|
dateColumn | headerName, useAsFilter | Renders a computed date value |
valueColumn | headerName, useAsFilter | Renders a computed non-date value |
derivedValueColumn | headerName, useAsFilter, basePatientQuery | Renders a value derived from another variable's result |
basePatientQuery (only on derivedValueColumn) references one or more comma-separated published patient query identifiers to merge into the column's inline query, the same mechanism Custom Dashboards inline tabs use for patientQueryReference.
Demographic columns (demographicInformationColumn)
Every row below also accepts headerName and useAsFilter (see Column reference above).
value | Description |
|---|---|
patientName | Patient full name |
patientPrimaryIdentifier | Primary identifier |
patientUuid | Medicus internal UUID |
patientNhsNumber | NHS number |
patientDateOfBirth | Date of birth |
patientAge | Current age |
patientGender | Gender |
patientRegistrationType | Registration type |
patientRegistrationDate | Registration date |
patientCurrentRegistrationType | Current registration type |
namedGP | Named GP |
homeAddress | Home address |
homePhoneNumber | Home phone |
mobilePhoneNumber | Mobile phone |
emailAddress | Email address |
currentRegistrationStartDate | Current registration start date |
dateOfDeath | Date of death |
prescriptionsDispensedByPractice | Whether dispensed by practice |
careHomeResident | Whether care home resident |
Coded entry columns (codedEntryValueColumn)
Every row below also accepts headerName and useAsFilter (see Column reference above).
value | Description |
|---|---|
activityDate | Date of the coded entry |
activity | Activity description |
codeDescription | SNOMED CT description |
activityTime | Time of the coded entry |
activityEndDate | End date |
activityStatus | Status |
value | Numeric observation value |
bodySite | Body site |
route | Administration route |
batchNumber | Batch number |
expiryDate | Expiry date |
responsiblePractitioner | Responsible practitioner name |
responsiblePractitionerIdentifier | Responsible practitioner identifier |
responsibleOrganizationOdsCode | Responsible organisation ODS code |
activityPerformedHere | Whether performed at the local organisation |
responsibleOrganisation | Responsible organisation name |
encounterType | Encounter type description |
encounterTypeCode | Encounter type SNOMED CT concept ID |
Appointment columns (appointmentValueColumn)
Every row below also accepts headerName and useAsFilter (see Column reference above).
value | Description |
|---|---|
appointmentDate | Intended start date/time |
appointmentEndDateTime | Intended end date/time |
appointmentStatus | Status |
appointmentTypeName | Appointment type name |
appointmentServiceName | Service name |
appointmentPractitionerName | Practitioner name |
appointmentDeliveryMode | Delivery mode |
appointmentDurationSeconds | Duration in seconds |
appointmentBookedDateTime | When booked |
appointmentArrivedDateTime | When patient arrived |
appointmentCancelledDateTime | When cancelled |
appointmentRescheduledDateTime | When rescheduled |
appointmentNhsSlotType | NHS GPAD slot type category |
appointmentSiteName | Site name |
appointmentRoomName | Room name |
Prescription columns (prescriptionValueColumn)
Every row below also accepts headerName and useAsFilter (see Column reference above).
value | Description |
|---|---|
prescriptionType | Type (acute, repeat, etc.) |
prescriptionStatus | Status |
prescriptionProductCode | SNOMED / dm+d code |
prescriptionProductDescription | Product description |
prescriptionStartDate | Start date |
prescriptionExpectedEndDate | Expected end date |
prescriptionLastIssueDate | Last issue date |
prescriptionDiscontinuedDate | Discontinued date |
prescriptionAuthorisedDate | Authorised date |
prescriptionAuthorisedByPractitioner | Authorising practitioner |
prescriptionDosageInstruction | Dosage instructions |
Prescription issue columns (prescriptionIssueValueColumn)
Every row below also accepts headerName and useAsFilter (see Column reference above).
value | Description |
|---|---|
prescriptionIssueProductCode | SNOMED / dm+d code |
prescriptionIssueProductDescription | Product description |
prescriptionIssueQuantity | Quantity |
prescriptionIssueDate | Issue date |
prescriptionIssueMethod | Issue method |
prescriptionIssueNumber | Issue number in sequence |
prescriptionIssueStatus | Issue status |
prescriptionIssueSupplyEndDate | Supply end date |
prescriptionIssueIssuedByPractitioner | Issuing practitioner |
prescriptionIssueExpectedDaysSupply | Expected days supply |
prescriptionIssueIssuingOrganisation | Issuing organisation |
prescriptionIssueEpsPharmacyOdsCode | EPS pharmacy ODS code |