diff --git a/CHANGELOG.md b/CHANGELOG.md
index a7242d3fff..b86253fd82 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [9.1.2] - Unreleased
+- Add CLI Command to export Catalogue Info to HDR Gateway (see [Documentation\Catalogues\ExportCataloguesToHDR.md])
+
## [9.1.1] - 2025-12-02
- Allow Atlassian service workers to write to Confluence from RDMP
diff --git a/Documentation/Catalogues/ExportCataloguesToHDR.md b/Documentation/Catalogues/ExportCataloguesToHDR.md
new file mode 100644
index 0000000000..3699fe60df
--- /dev/null
+++ b/Documentation/Catalogues/ExportCataloguesToHDR.md
@@ -0,0 +1,30 @@
+## Export Catalogues to HDR
+
+The [HDR Gateway](https://healthdatagateway.org/en) is a metadata catalogue for all things health data.
+RDMP has the ability to upload all non-internal, non-depricated, non-project specific catalogues to it using the following command
+```
+rdmp.exe exportcataloguestohdrgateway https://api.hdruk.cloud/api {teamID} {AppID} {ClientID} {additional config file}
+
+```
+
+Running this command will create new dataets on the HDR Gateway, as well as update any existing datasets with the same name as the catalogue.
+
+### Command Variables
+* teamId - the TeamID that you and the dataset(s) you want to create belong to
+* AppID - This integration requires the use of a custom integration, the AppID is provided by this integration
+* ClientID- This integration requires the use of a custom integration, the clientID is provided by this integration
+* Additional config file - some details are not stored within RDMP, but are typically the same for all datasets that belong to a single owner. This file is used to populate these fields. Use the example below as a starting point
+
+### Example Config File
+ {
+ "accessRights":"https://www.dundee.ac.uk/hic/governance-service",
+ "accessService":"HIC has implemented a remote-access Trusted Research Environment to protect data confidentiality, satisfy public concerns about data loss and reassure Data Controllers about HIC’s secure management and processing of their data.\r\nData is not released externally to data users for analysis on their own computers but placed on a server at HIC, within a restricted, secure IT environment, where the data user is given secure remote access to carry out their analysis.\r\nFull details are available via the following link: https://www.dundee.ac.uk/hic/safe-haven",
+ "accessServiceCategory":"TRE/SDE",
+ "conformsTo":["LOCAL"],
+ "vocabularyEncodingScheme":["LOCAL"],
+ "language":["en"],
+ "format":["CSV","Database"]
+ "dataUseLimitation":["General research use"],
+ "resourceCreator":"Please cite us!",
+ "dataUseRequirements":["Disclosure control"]
+ }
diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandExportCataloguesToHDRGateway.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandExportCataloguesToHDRGateway.cs
new file mode 100644
index 0000000000..2d7ac3f366
--- /dev/null
+++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandExportCataloguesToHDRGateway.cs
@@ -0,0 +1,751 @@
+
+using MongoDB.Driver;
+using Newtonsoft.Json;
+using Rdmp.Core.Curation.Data;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace Rdmp.Core.CommandExecution.AtomicCommands
+{
+ ///
+ /// Uses provided HDR Gateway credentials to populate the HDR gateway
+ ///
+ public class ExecuteCommandExportCataloguesToHDRGateway : BasicCommandExecution, IAtomicCommand
+ {
+
+ private readonly IBasicActivateItems _activator;
+ private readonly string _url;
+ private readonly int _team;
+ private readonly string _appID;
+ private readonly string _clientID;
+ private readonly HttpClient _client = new();
+ private readonly string _datasetsEndpoint = "/v2/datasets";
+ private readonly InputJSONDetails _inputJsonDetails = new();
+ private class InputJSONDetails
+ {
+ ///
+ /// {
+ /// "accessRights":"https://www.dundee.ac.uk/hic/governance-service",
+ /// "accessService":"some kind of access message- get in touch",
+ /// "accessServiceCategory":"TRE/SDE",
+ /// "conformsTo":["LOCAL"],
+ /// "vocabularyEncodingScheme":["LOCAL"],
+ /// "language":["en"],
+ /// "format":["CSV","Database"]
+ /// "dataUseLimitation":["General research use"],
+ /// "resourceCreator":"Please cite us!",
+ /// "dataUseRequirements":["Disclosure control"]
+ /// }
+ ///
+ public string accessRights { get; set; }
+ public string accessService { get; set; }
+ public string accessRequestCost { get; set; }
+ public string accessServiceCategory { get; set; }
+ public List conformsTo { get; set; }
+ public List vocabularyEncodingScheme { get; set; }
+ public List language { get; set; }
+ public List format { get; set; }
+ public List dataUseLimitation { get; set; }
+ public string resourceCreator { get; set; }
+ public List dataUseRequirements { get; set; }
+ }
+
+ public class HDRDatasetDataCustodian
+ {
+ public HDRDatasetDataCustodian() { }
+ public HDRDatasetDataCustodian(Catalogue catalogue)
+ {
+ name = catalogue.DataProcessor;
+ contactPoint = catalogue.Administrative_contact_email;
+ }
+ public string name { get; set; }
+ public int identifier { get; set; } = 0;
+ public string contactPoint { get; set; }
+ public object logo { get; set; }
+ public object description { get; set; }
+ public object memberOf { get; set; }
+ }
+
+ public class HDRDatasetDocumentation
+ {
+ public HDRDatasetDocumentation() { }
+ public HDRDatasetDocumentation(Catalogue catalogue)
+ {
+ description = catalogue.Description;
+ }
+
+ public void Update(Catalogue catalogue)
+ {
+ description = catalogue.Description;
+
+ }
+ public string description { get; set; }
+ }
+ private class HDRDatasetMetadataSummary
+ {
+ public HDRDatasetMetadataSummary(Catalogue catalogue)
+ {
+ Update(catalogue);
+ }
+ public void Update(Catalogue catalogue)
+ {
+ title = catalogue.Name;
+ populationSize = 0;
+ @abstract = catalogue.ShortDescription;
+ contactPoint = catalogue.Administrative_contact_email;
+ dataCustodian = new HDRDatasetDataCustodian(catalogue);
+ keywords = catalogue.Search_keywords != null ? catalogue.Search_keywords.Split(',').ToList() : null;
+ doiName = catalogue.Doi;
+ }
+
+ public HDRDatasetMetadataSummary() { }
+ public string @abstract { get; set; }
+ public string contactPoint { get; set; }
+ public object keywords { get; set; }
+ public object doiName { get; set; }
+ public string title { get; set; }
+ public HDRDatasetDataCustodian dataCustodian { get; set; } = new HDRDatasetDataCustodian();
+ public int populationSize { get; set; }
+ public object alternateIdentifiers { get; set; }
+ public object controlledKeywords { get; set; }
+ }
+ private class HDRDatasetAccess
+ {
+
+ private static string MapUpdateLag(Catalogue.UpdateLagTimes time)
+ {
+ switch (time)
+ {
+ case Catalogue.UpdateLagTimes.LessThanAWeek:
+ return "Less than 1 week";
+ case Catalogue.UpdateLagTimes.OneToTwoWeeks:
+ return "1-2 weeks";
+ case Catalogue.UpdateLagTimes.TwoToFourWeeks:
+ return "2-4 weeks";
+ case Catalogue.UpdateLagTimes.OneToTwoMonths:
+ return "1-2 months";
+ case Catalogue.UpdateLagTimes.TwoToSixMonths:
+ return "2-6 months";
+ case Catalogue.UpdateLagTimes.SixMonthsPlus:
+ return "More than 6 months";
+ case Catalogue.UpdateLagTimes.Variable:
+ return "Variable";
+ case Catalogue.UpdateLagTimes.NotApplicable:
+ return "Not applicable";
+ case Catalogue.UpdateLagTimes.Other:
+ return "Other";
+ }
+ return null;
+ }
+
+ public HDRDatasetAccess() { }
+ public HDRDatasetAccess(Catalogue catalogue, InputJSONDetails details)
+ {
+ Update(catalogue, details);
+ }
+
+ public void Update(Catalogue catalogue, InputJSONDetails details)
+ {
+ jurisdiction = catalogue.Juristiction != null ? catalogue.Juristiction.Split(',').ToList() : null;
+ deliveryLeadTime = MapUpdateLag(catalogue.UpdateLag);
+ dataController = catalogue.DataController;
+ dataProcessor = catalogue.DataProcessor;
+ accessRights = details.accessRights;
+ accessService = details.accessService;
+ accessServiceCategory = details.accessServiceCategory;
+ }
+
+ public string deliveryLeadTime { get; set; }
+ public object jurisdiction { get; set; }
+ public string dataController { get; set; }
+ public string dataProcessor { get; set; }
+ public string accessRights { get; set; }
+ public string accessService { get; set; }
+ public string accessRequestCost { get; set; }
+ public object accessServiceCategory { get; set; }
+ }
+
+ private class CustomDateTimeConverterThreeMilliseconds : JsonConverter
+ {
+ public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
+ {
+ return existingValue;
+ }
+
+
+ public override void WriteJson(JsonWriter writer, DateTime value, Newtonsoft.Json.JsonSerializer serializer)
+ {
+ writer.WriteValue(value.ToString("yyyy-MM-ddTHH:mm:ss.000Z", CultureInfo.InvariantCulture));
+ }
+ }
+
+ public class HDRDatasetTemporal
+ {
+ public HDRDatasetTemporal() { }
+ public HDRDatasetTemporal(Catalogue catalogue)
+ {
+ timeLag = catalogue.UpdateLag.ToString();
+ publishingFrequency = catalogue.Update_freq.ToString();
+ if (catalogue.StartDate != null) startDate = (DateTime)catalogue.StartDate;
+ if (catalogue.EndDate != null) endDate = (DateTime)catalogue.EndDate;
+
+ }
+
+ public void Update(Catalogue catalogue)
+ {
+ timeLag = catalogue.UpdateLag.ToString();
+ publishingFrequency = catalogue.Update_freq.ToString();
+ if (catalogue.StartDate != null) startDate = (DateTime)catalogue.StartDate;
+ if (catalogue.EndDate != null) endDate = (DateTime)catalogue.EndDate;
+ if (catalogue.DatasetReleaseDate != null) distributionReleaseDate = (DateTime)catalogue.DatasetReleaseDate;
+ }
+
+ [JsonConverter(typeof(CustomDateTimeConverterThreeMilliseconds))]
+ public DateTime? endDate { get; set; }
+
+ [JsonConverter(typeof(CustomDateTimeConverterThreeMilliseconds))]
+ public DateTime startDate { get; set; }
+ public string timeLag { get; set; }
+ public string publishingFrequency { get; set; }
+ public object distributionReleaseDate { get; set; }
+ }
+
+ private class HDRDatasetFormatAndStandards
+ {
+ public HDRDatasetFormatAndStandards() { }
+ public HDRDatasetFormatAndStandards(Catalogue catalogue, InputJSONDetails details)
+ {
+ Update(catalogue, details);
+ }
+ public void Update(Catalogue catalogue, InputJSONDetails details)
+ {
+ conformsTo = details.conformsTo;
+ vocabularyEncodingScheme = details.vocabularyEncodingScheme;
+ language = details.language;
+ format = details.format;
+ }
+ public object conformsTo { get; set; }
+ public object vocabularyEncodingScheme { get; set; }
+ public object language { get; set; }
+ public object format { get; set; }
+ }
+
+ public class HDRDatasetOrigin
+ {
+
+
+ public HDRDatasetOrigin() { }
+
+
+ private static string MapDatasetType(string datasetType)
+ {
+ switch (datasetType)
+ {
+ case "HealthcareAndDisease":
+ return "Health and disease";
+ case "TreatmentsAndInterventions":
+ return "Treatments/Interventions";
+ case "MeasurementsAndTests":
+ return "Measurements/Tests";
+ case "ImagingTypes":
+ return "Imaging Types";
+ case "Omics":
+ return "Omics";
+ case "Socioeconomic":
+ return "Socioeconomic";
+ case "Lifestyle":
+ return "Lifestyle";
+ case "Registry":
+ return "Registry";
+ case "EnvironmentalAndEnergy":
+ return "Environment and energy";
+ case "InformationAndCommunication":
+ return "Information and communication";
+ case "Politics":
+ return "Politics";
+ }
+ return "";
+ }
+
+ private static string MapDataSourceType(string dataSourceType)
+ {
+ switch (dataSourceType)
+ {
+ case "Other":
+ return "Other";
+ case "EPR":
+ return "EPR";
+ case "ElectronicSurvey":
+ return "Electronic survey";
+ case "LIMS":
+ return "LIMS";
+ case "PaperBased":
+ return "Paper-based";
+ case "FreeTextNLP":
+ return "Free text NLP";
+ case "MachineLearning":
+ return "Machine generated";
+ }
+ return "";
+ }
+
+ private static string MapPurpose(Catalogue.DatasetPurpose purpose)
+ {
+ switch (purpose)
+ {
+
+
+ case Catalogue.DatasetPurpose.ResearchCohort:
+ return "Research cohort";
+ case Catalogue.DatasetPurpose.Study:
+ return "Study";
+ case Catalogue.DatasetPurpose.DiseaseRegistry:
+ return "Disease registry";
+ case Catalogue.DatasetPurpose.Trial:
+ return "Trial";
+ case Catalogue.DatasetPurpose.Care:
+ return "Care";
+ case Catalogue.DatasetPurpose.Audit:
+ return "Audit";
+ case Catalogue.DatasetPurpose.Administrative:
+ return "Administrative";
+ case Catalogue.DatasetPurpose.Finantial:
+ return "Financial";
+ case Catalogue.DatasetPurpose.Statutory:
+ return "Statutory";
+ case Catalogue.DatasetPurpose.Other:
+ return "Other";
+ }
+ return null;
+ }
+
+ private class DataTypeObject
+ {
+ public string name;
+ public List subTypes = [];
+ }
+
+ private static string MapCollectionSource(string source)
+ {
+ switch (source)
+ {
+ case "CohortStudyTrial":
+ return "Cohort, study, trial";
+ case "Clininc":
+ return "Clinic";
+ case "PrimaryCareReferrals":
+ return "Primary care - Referrals";
+ case "PrimaryCareClinic":
+ return "Primary care - Clinic";
+ case "PrimaryCareOutOfHours":
+ return "Primary care - Out of hours";
+ case "SecondaryCareAccidentAndEmergency":
+ return "Secondary care - Accident and Emergency";
+ case "SecondaryCareOutpatients":
+ return "Secondary care - Outpatients";
+ case "SecondaryCareInPateints":
+ return "Secondary care - In-patients";
+ case "SecondaryCareAmbulance":
+ return "Secondary care - Ambulance";
+ case "SecondaryCareICU":
+ return "Secondary care - ICU";
+ case "PrescribingCommunityPharmacy":
+ return "Prescribing - Community pharmacy";
+ case "PrescribingHospital":
+ return "Prescribing - Hospital";
+ case "PateintReportOutcome":
+ return "Patient report outcome";
+ case "Wearables":
+ return "Wearables";
+ case "LocalAuthority":
+ return "Local authority";
+ case "NationalGovernment":
+ return "National government";
+ case "Community":
+ return "Community";
+ case "Services":
+ return "Services";
+ case "Home":
+ return "Home";
+ case "Private":
+ return "Private";
+ case "SocialCareHealthcareAtHome":
+ return "Social care - Health care at home";
+ case "SocialCareOthersocialData":
+ return "Social care - Other social data";
+ case "Census":
+ return "Census";
+ case "Other":
+ return "Other";
+ }
+ return null;
+ }
+
+ public HDRDatasetOrigin(Catalogue catalogue)
+ {
+ Update(catalogue);
+ }
+
+ public void Update(Catalogue catalogue)
+ {
+ source = catalogue.DataSource != null ? catalogue.DataSource.Split(',').Select(s => MapDataSourceType(s)).Where(s => s != "") : null;
+ purpose = new List() { MapPurpose(catalogue.Purpose) };
+ collectionSource = catalogue.DataSourceSetting != null ? catalogue.DataSourceSetting.Split(',').Select(source => MapCollectionSource(source)) : null;
+ datasetType = catalogue.DataType != null ? catalogue.DataType.Split(',').Select(s => MapDatasetType(s)).Where(s => s != "").Select(s => new DataTypeObject() { name = s }) : null;
+
+ }
+ public object purpose { get; set; }
+ public object source { get; set; }
+ public object collectionSource { get; set; }
+ public object datasetType { get; set; }
+ public string datasetSubType { get; set; }
+ public string imageContrast { get; set; }
+ }
+
+ private class HDRDatasetUsage
+ {
+ public HDRDatasetUsage() { }
+ public HDRDatasetUsage(InputJSONDetails details)
+ {
+ dataUseLimitation = details.dataUseLimitation;
+ resourceCreator = details.resourceCreator;
+ dataUseRequirements = details.dataUseRequirements;
+ }
+
+ public object dataUseLimitation { get; set; }
+ public object resourceCreator { get; set; }
+ public List dataUseRequirements { get; set; }
+ }
+ private class HDRDatasetAccessibility
+ {
+ public HDRDatasetAccessibility() { }
+
+ public HDRDatasetAccessibility(Catalogue catalogue, InputJSONDetails details)
+ {
+ access = new HDRDatasetAccess(catalogue, details);
+ formatAndStandards = new HDRDatasetFormatAndStandards(catalogue, details);
+ usage = new HDRDatasetUsage(details);
+ }
+
+ public void Update(Catalogue catalogue, InputJSONDetails details)
+ {
+ access = new HDRDatasetAccess(catalogue, details);
+ formatAndStandards.Update(catalogue, details);
+ usage = new HDRDatasetUsage(details);
+ }
+ public object access { get; set; }
+ public HDRDatasetUsage usage { get; set; }
+ public HDRDatasetFormatAndStandards formatAndStandards { get; set; } = new HDRDatasetFormatAndStandards();
+ }
+ private class HDRDatasetProvenance
+ {
+ public HDRDatasetProvenance() { }
+
+ public HDRDatasetProvenance(Catalogue catalogue)
+ {
+ temporal = new HDRDatasetTemporal(catalogue);
+ origin = new HDRDatasetOrigin(catalogue);
+ }
+
+ public void Update(Catalogue catalogue)
+ {
+ temporal.Update(catalogue);
+ if (origin != null) origin.Update(catalogue);
+ }
+ public HDRDatasetOrigin origin { get; set; }
+ public HDRDatasetTemporal temporal { get; set; } = new HDRDatasetTemporal();
+ }
+ public class HDRDatasetRevision
+ {
+ public string version { get; set; }
+ public string url { get; set; }
+ }
+
+ private class HDRDatasetCoverage
+ {
+
+ public HDRDatasetCoverage() { }
+
+ public HDRDatasetCoverage(Catalogue catalogue)
+ {
+ spatial = catalogue.Geographical_coverage;
+ }
+
+ public void Update(Catalogue catalogue)
+ {
+ spatial = catalogue.Geographical_coverage;
+ }
+ public string spatial { get; set; }
+ }
+
+ private class HDRDatasetRequired
+ {
+
+ public HDRDatasetRequired() { }
+
+ public string issued { get; set; }
+ public string version { get; set; }
+ public string modified { get; set; }
+ public string gatewayId { get; set; }
+ public string gatewayPid { get; set; }
+
+ public List revisions { get; set; } = new();
+ }
+
+ private class HDRDatasetMetadata
+ {
+
+ public HDRDatasetMetadata(Catalogue catalogue, InputJSONDetails details)
+ {
+ summary = new HDRDatasetMetadataSummary(catalogue);
+ documentation = new HDRDatasetDocumentation(catalogue);
+ provenance = new HDRDatasetProvenance(catalogue);
+ coverage = new HDRDatasetCoverage(catalogue);
+ accessibility = new HDRDatasetAccessibility(catalogue, details);
+ }
+
+ public HDRDatasetMetadata() { }
+ public string identifier = "";
+ public string version = "1.0.0";
+ public List revisions = new List();
+ public string modified = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
+ public string issued = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
+ public HDRDatasetMetadataSummary summary = new HDRDatasetMetadataSummary();
+ public HDRDatasetAccessibility accessibility = new HDRDatasetAccessibility();
+ public List