Skip to main content
Resource Publishing

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 typeRoot elementRow 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

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>&lt;&lt; 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>

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>&lt;&lt; 44054006 |Diabetes mellitus type 2|</inclusion>
<exclusion>&lt;&lt; 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>&lt;&lt; 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>&lt;&lt; 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>&lt;&lt; 314764000 |Diabetes mellitus annual review|</inclusion>
</rules>
</codeListDefinition>
</codeList>
<codeList>
<name>CRITERION_2_COD</name>
<codeListDefinition>
<rules>
<inclusion>&lt;&lt; 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">
<!--
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:simpleType name="codedEntryValueType">
<xs:restriction base="xs:string">
<xs:enumeration value="activityDate"/>
<xs:enumeration value="activity"/>
<xs:enumeration value="codeDescription"/>
<xs:enumeration value="activityTime"/>
<xs:enumeration value="activityEndDate"/>
<xs:enumeration value="activityStatus"/>
<xs:enumeration value="value"/>
<xs:enumeration value="bodySite"/>
<xs:enumeration value="route"/>
<xs:enumeration value="batchNumber"/>
<xs:enumeration value="expiryDate"/>
<xs:enumeration value="responsiblePractitioner"/>
<xs:enumeration value="responsiblePractitionerIdentifier"/>
<xs:enumeration value="responsibleOrganizationOdsCode"/>
<xs:enumeration value="activityPerformedHere"/>
<xs:enumeration value="responsibleOrganisation"/>
<xs:enumeration value="encounterType"/>
<xs:enumeration value="encounterTypeCode"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="demographicInformationValueType">
<xs:restriction base="xs:string">
<xs:enumeration value="patientName"/>
<xs:enumeration value="patientPrimaryIdentifier"/>
<xs:enumeration value="patientUuid"/>
<xs:enumeration value="patientNhsNumber"/>
<xs:enumeration value="patientDateOfBirth"/>
<xs:enumeration value="patientAge"/>
<xs:enumeration value="patientGender"/>
<xs:enumeration value="patientRegistrationType"/>
<xs:enumeration value="patientRegistrationDate"/>
<xs:enumeration value="patientCurrentRegistrationType"/>
<xs:enumeration value="namedGP"/>
<xs:enumeration value="homeAddress"/>
<xs:enumeration value="homePhoneNumber"/>
<xs:enumeration value="mobilePhoneNumber"/>
<xs:enumeration value="emailAddress"/>
<xs:enumeration value="currentRegistrationStartDate"/>
<xs:enumeration value="dateOfDeath"/>
<xs:enumeration value="prescriptionsDispensedByPractice"/>
<xs:enumeration value="careHomeResident"/>
</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:simpleType name="prescriptionValueType">
<xs:restriction base="xs:string">
<xs:enumeration value="prescriptionType"/>
<xs:enumeration value="prescriptionStatus"/>
<xs:enumeration value="prescriptionProductCode"/>
<xs:enumeration value="prescriptionProductDescription"/>
<xs:enumeration value="prescriptionStartDate"/>
<xs:enumeration value="prescriptionExpectedEndDate"/>
<xs:enumeration value="prescriptionLastIssueDate"/>
<xs:enumeration value="prescriptionDiscontinuedDate"/>
<xs:enumeration value="prescriptionAuthorisedDate"/>
<xs:enumeration value="prescriptionAuthorisedByPractitioner"/>
<xs:enumeration value="prescriptionDosageInstruction"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="prescriptionIssueValueType">
<xs:restriction base="xs:string">
<xs:enumeration value="prescriptionIssueProductCode"/>
<xs:enumeration value="prescriptionIssueProductDescription"/>
<xs:enumeration value="prescriptionIssueQuantity"/>
<xs:enumeration value="prescriptionIssueDate"/>
<xs:enumeration value="prescriptionIssueMethod"/>
<xs:enumeration value="prescriptionIssueNumber"/>
<xs:enumeration value="prescriptionIssueStatus"/>
<xs:enumeration value="prescriptionIssueSupplyEndDate"/>
<xs:enumeration value="prescriptionIssueIssuedByPractitioner"/>
<xs:enumeration value="prescriptionIssueExpectedDaysSupply"/>
<xs:enumeration value="prescriptionIssueIssuingOrganisation"/>
</xs:restriction>
</xs:simpleType>
<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:simpleType name="appointmentValueType">
<xs:restriction base="xs:string">
<xs:enumeration value="appointmentDate"/>
<xs:enumeration value="appointmentEndDateTime"/>
<xs:enumeration value="appointmentStatus"/>
<xs:enumeration value="appointmentTypeName"/>
<xs:enumeration value="appointmentServiceName"/>
<xs:enumeration value="appointmentPractitionerName"/>
<xs:enumeration value="appointmentDeliveryMode"/>
<xs:enumeration value="appointmentDurationSeconds"/>
<xs:enumeration value="appointmentBookedDateTime"/>
<xs:enumeration value="appointmentArrivedDateTime"/>
<xs:enumeration value="appointmentCancelledDateTime"/>
<xs:enumeration value="appointmentRescheduledDateTime"/>
<xs:enumeration value="appointmentNhsSlotType"/>
<xs:enumeration value="appointmentSiteName"/>
<xs:enumeration value="appointmentRoomName"/>
</xs:restriction>
</xs:simpleType>
<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>
<xs:complexType name="columnsType">
<xs:choice minOccurs="1" maxOccurs="unbounded">
<xs:element name="demographicInformationColumn" type="demographicInformationColumnType"/>
<xs:element name="codedEntryValueColumn" type="codedEntryValueColumnType"/>
<xs:element name="dateColumn" type="dateColumnType"/>
<xs:element name="derivedValueColumn" type="derivedValueColumnType"/>
<xs:element name="valueColumn" type="valueColumnType"/>
<xs:element name="prescriptionValueColumn" type="prescriptionValueColumnType"/>
<xs:element name="prescriptionIssueValueColumn" type="prescriptionIssueValueColumnType"/>
<xs:element name="appointmentValueColumn" type="appointmentValueColumnType"/>
</xs:choice>
</xs:complexType>
<!-- 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="patientNamedGp" 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:choice>
</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="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="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="runtimeValue" type="xs:string" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="codedEntryDateVariableType">
<xs:attribute name="name" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="code" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="select" use="required"/>
<xs:attribute name="order" 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"/>
</xs:complexType>
<xs:complexType name="prescriptionVariableType">
<xs:attribute name="name" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="productCode" type="nonEmptyTrimmedString" use="optional"/>
<xs:attribute name="select" use="required"/>
<xs:attribute name="order" 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="productCode" type="nonEmptyTrimmedString" use="optional"/>
<xs:attribute name="select" use="required"/>
<xs:attribute name="order" 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:complexType>
<xs:complexType name="appointmentVariableType">
<xs:attribute name="name" type="nonEmptyTrimmedString" use="required"/>
<xs:attribute name="select" use="required"/>
<xs:attribute name="order" 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="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>
<!--
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="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="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="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>&lt;&lt; 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: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>

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: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: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: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>&lt;&lt; 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: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>

Column reference

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.

ElementAttributesDescription
dateColumnheaderName, useAsFilterRenders a computed date value
valueColumnheaderName, useAsFilterRenders a computed non-date value
derivedValueColumnheaderName, useAsFilter, basePatientQueryRenders 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)

valueDescription
patientNamePatient full name
patientPrimaryIdentifierPrimary identifier
patientUuidMedicus internal UUID
patientNhsNumberNHS number
patientDateOfBirthDate of birth
patientAgeCurrent age
patientGenderGender
patientRegistrationTypeRegistration type
patientRegistrationDateRegistration date
patientCurrentRegistrationTypeCurrent registration type
namedGPNamed GP
homeAddressHome address
homePhoneNumberHome phone
mobilePhoneNumberMobile phone
emailAddressEmail address
currentRegistrationStartDateCurrent registration start date
dateOfDeathDate of death
prescriptionsDispensedByPracticeWhether dispensed by practice
careHomeResidentWhether care home resident

Coded entry columns (codedEntryValueColumn)

valueDescription
activityDateDate of the coded entry
activityActivity description
codeDescriptionSNOMED CT description
activityTimeTime of the coded entry
activityEndDateEnd date
activityStatusStatus
valueNumeric observation value
bodySiteBody site
routeAdministration route
batchNumberBatch number
expiryDateExpiry date
responsiblePractitionerResponsible practitioner name
responsiblePractitionerIdentifierResponsible practitioner identifier
responsibleOrganizationOdsCodeResponsible organisation ODS code
activityPerformedHereWhether performed at the local organisation
responsibleOrganisationResponsible organisation name
encounterTypeEncounter type description
encounterTypeCodeEncounter type SNOMED CT concept ID

Appointment columns (appointmentValueColumn)

valueDescription
appointmentDateIntended start date/time
appointmentEndDateTimeIntended end date/time
appointmentStatusStatus
appointmentTypeNameAppointment type name
appointmentServiceNameService name
appointmentPractitionerNamePractitioner name
appointmentDeliveryModeDelivery mode
appointmentDurationSecondsDuration in seconds
appointmentBookedDateTimeWhen booked
appointmentArrivedDateTimeWhen patient arrived
appointmentCancelledDateTimeWhen cancelled
appointmentRescheduledDateTimeWhen rescheduled
appointmentNhsSlotTypeNHS GPAD slot type category
appointmentSiteNameSite name
appointmentRoomNameRoom name

Prescription columns (prescriptionValueColumn)

valueDescription
prescriptionTypeType (acute, repeat, etc.)
prescriptionStatusStatus
prescriptionProductCodeSNOMED / dm+d code
prescriptionProductDescriptionProduct description
prescriptionStartDateStart date
prescriptionExpectedEndDateExpected end date
prescriptionLastIssueDateLast issue date
prescriptionDiscontinuedDateDiscontinued date
prescriptionAuthorisedDateAuthorised date
prescriptionAuthorisedByPractitionerAuthorising practitioner
prescriptionDosageInstructionDosage instructions

Prescription issue columns (prescriptionIssueValueColumn)

valueDescription
prescriptionIssueProductCodeSNOMED / dm+d code
prescriptionIssueProductDescriptionProduct description
prescriptionIssueQuantityQuantity
prescriptionIssueDateIssue date
prescriptionIssueMethodIssue method
prescriptionIssueNumberIssue number in sequence
prescriptionIssueStatusIssue status
prescriptionIssueSupplyEndDateSupply end date
prescriptionIssueIssuedByPractitionerIssuing practitioner
prescriptionIssueExpectedDaysSupplyExpected days supply
prescriptionIssueIssuingOrganisationIssuing organisation