Skip to content

Commit

Permalink
feature: #964: Report discrepant results from exercise 1 of wmda cons…
Browse files Browse the repository at this point in the history
…ensus dataset.
  • Loading branch information
zabeen committed May 16, 2023
1 parent 13deb52 commit bbf42c6
Show file tree
Hide file tree
Showing 17 changed files with 559 additions and 65 deletions.
64 changes: 64 additions & 0 deletions Atlas.ManualTesting.Common/FileReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using CsvHelper;

namespace Atlas.ManualTesting.Common
{
public interface IFileReader<T>
{
Task<IReadOnlyCollection<T>> ReadAllLines(string delimiter, string filePath);
IAsyncEnumerable<T> ReadAsync(string delimiter, string filePath);
}

public class FileReader<T> : IFileReader<T>
{
public async Task<IReadOnlyCollection<T>> ReadAllLines(string delimiter, string filePath)
{
FileChecks(filePath);

await using var stream = File.OpenRead(filePath);
using var reader = new StreamReader(stream);
using var csv = new CsvReader(reader);

csv.Configuration.Delimiter = delimiter;
csv.Configuration.HeaderValidated = null;
csv.Configuration.MissingFieldFound = null;
csv.Configuration.TypeConverterOptionsCache.GetOptions<string>().NullValues.Add("");

return csv.GetRecords<T>().ToList();
}

public async IAsyncEnumerable<T> ReadAsync(string delimiter, string filePath)
{
FileChecks(filePath);

await using var stream = File.OpenRead(filePath);
using var reader = new StreamReader(stream);
using var csv = new CsvReader(reader);

csv.Configuration.Delimiter = delimiter;
csv.Configuration.HeaderValidated = null;
csv.Configuration.MissingFieldFound = null;
csv.Configuration.TypeConverterOptionsCache.GetOptions<string>().NullValues.Add("");

await csv.ReadAsync();
csv.ReadHeader();

while (await csv.ReadAsync())
{
yield return csv.GetRecord<T>();
}
}

private static void FileChecks(string filePath)
{
if (string.IsNullOrEmpty(filePath))
{
throw new ArgumentNullException(nameof(filePath));
}

if (!File.Exists(filePath))
{
throw new ArgumentException($"File not found at {filePath}.");
}
}
}
}
36 changes: 0 additions & 36 deletions Atlas.ManualTesting.Common/SubjectImport/SubjectInfoReader.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
using Atlas.Common.Utils.Extensions;
using Atlas.DonorImport.Data.Repositories;
using Atlas.DonorImport.ExternalInterface.Models;
using Atlas.ManualTesting.Common.SubjectImport;
using Atlas.ManualTesting.Common;
using Atlas.ManualTesting.Services;
using Atlas.ManualTesting.Services.Scoring;
using Atlas.ManualTesting.Services.ServiceBus;
using Atlas.ManualTesting.Services.WmdaConsensusResults;
using Atlas.ManualTesting.Settings;
using Atlas.MatchingAlgorithm.Common.Models;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -33,6 +34,7 @@ public static void RegisterServices(this IServiceCollection services)

private static void RegisterSettings(this IServiceCollection services)
{
services.RegisterAsOptions<HlaMetadataDictionarySettings>("HlaMetadataDictionary");
services.RegisterAsOptions<MessagingServiceBusSettings>("MessagingServiceBus");
services.RegisterAsOptions<MatchingSettings>("Matching");
services.RegisterAsOptions<DonorManagementSettings>("Matching:DonorManagement");
Expand Down Expand Up @@ -84,9 +86,12 @@ Func<IServiceProvider, DonorManagementSettings> fetchDonorManagementSettings

services.AddScoped<IDonorStoresInspector, DonorStoresInspector>();

services.AddScoped<ISubjectInfoReader, SubjectInfoReader>();
services.AddScoped(typeof(IFileReader<>), typeof(FileReader<>));
services.AddScoped<IScoreBatchRequester, ScoreBatchRequester>();
services.AddScoped<IScoreRequestProcessor, ScoreRequestProcessor>();
services.AddScoped<IWmdaResultsComparer, WmdaResultsComparer>();
services.AddScoped<IWmdaDiscrepantResultsReporter, WmdaDiscrepantResultsReporter>();
services.AddScoped<IConvertHlaRequester, ConvertHlaRequester>();
}

private static void RegisterDatabaseServices(
Expand Down
17 changes: 16 additions & 1 deletion Atlas.ManualTesting/Functions/WmdaConsensusDatasetFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Atlas.Common.Public.Models.GeneticData;
using Atlas.ManualTesting.Models;
using Atlas.ManualTesting.Services.Scoring;
using Atlas.ManualTesting.Services.WmdaConsensusResults;
using AzureFunctions.Extensions.Swashbuckle.Attribute;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs;
Expand All @@ -19,10 +20,14 @@ namespace Atlas.ManualTesting.Functions
public class WmdaConsensusDatasetFunctions
{
private readonly IScoreRequestProcessor scoreRequestProcessor;
private readonly IWmdaDiscrepantResultsReporter wmdaDiscrepantResultsReporter;

public WmdaConsensusDatasetFunctions(IScoreRequestProcessor scoreRequestProcessor)
public WmdaConsensusDatasetFunctions(
IScoreRequestProcessor scoreRequestProcessor,
IWmdaDiscrepantResultsReporter wmdaDiscrepantResultsReporter)
{
this.scoreRequestProcessor = scoreRequestProcessor;
this.wmdaDiscrepantResultsReporter = wmdaDiscrepantResultsReporter;
}

[FunctionName(nameof(ProcessWmdaConsensusDataset_Exercise1))]
Expand Down Expand Up @@ -57,6 +62,16 @@ await scoreRequestProcessor.ProcessScoreRequest(new ScoreRequestProcessorInput
});
}

[FunctionName(nameof(ReportDiscrepantResults_Exercise1))]
public async Task ReportDiscrepantResults_Exercise1(
[RequestBodyType(typeof(ReportDiscrepanciesRequest), nameof(ReportDiscrepanciesRequest))]
[HttpTrigger(AuthorizationLevel.Function, "post")]
HttpRequest request)
{
var importAndCompareRequest = JsonConvert.DeserializeObject<ReportDiscrepanciesRequest>(await new StreamReader(request.Body).ReadToEndAsync());
await wmdaDiscrepantResultsReporter.ReportDiscrepantResults(importAndCompareRequest);
}

private static ScoringCriteria BuildThreeLocusScoringCriteria()
{
return new ScoringCriteria
Expand Down
6 changes: 2 additions & 4 deletions Atlas.ManualTesting/Models/ImportedSubjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
using Atlas.Common.Public.Models.GeneticData.PhenotypeInfo;
using Atlas.Common.Public.Models.GeneticData.PhenotypeInfo.TransferModels;
using Atlas.ManualTesting.Common.SubjectImport;

namespace Atlas.ManualTesting.Models
{
internal static class ImportedSubjectExtensions
{
public static PhenotypeInfoTransfer<string> ToPhenotypeInfoTransfer(this ImportedSubject subject)
public static PhenotypeInfo<string> ToPhenotypeInfo(this ImportedSubject subject)
{
return new PhenotypeInfo<string>(
valueA_1: subject.A_1,
Expand All @@ -18,8 +17,7 @@ public static PhenotypeInfoTransfer<string> ToPhenotypeInfoTransfer(this Importe
valueDqb1_1: subject.DQB1_1,
valueDqb1_2: subject.DQB1_2,
valueDrb1_1: subject.DRB1_1,
valueDrb1_2: subject.DRB1_2)
.ToPhenotypeInfoTransfer();
valueDrb1_2: subject.DRB1_2);
}
}
}
25 changes: 25 additions & 0 deletions Atlas.ManualTesting/Models/ReportDiscrepanciesRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Atlas.ManualTesting.Models
{
public class ReportDiscrepanciesRequest
{
/// <summary>
/// Path to file with consensus results
/// </summary>
public string ConsensusFilePath { get; set; }

/// <summary>
/// Path to file with Atlas results that should be compared to the <see cref="ConsensusFilePath"/>
/// </summary>
public string ResultsFilePath { get; set; }

/// <summary>
/// Path to file containing patient HLA
/// </summary>
public string PatientFilePath { get; set; }

/// <summary>
/// Path to file containing donor HLA
/// </summary>
public string DonorFilePath { get; set; }
}
}
25 changes: 16 additions & 9 deletions Atlas.ManualTesting/Models/WmdaConsensusResultsFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@ public class WmdaConsensusResultsFile
{
public string PatientId { get; set; }
public string DonorId { get; set; }
public int? MismatchCountAtA { get; set; }
public int? MismatchCountAtB { get; set; }
public int? MismatchCountAtDrb1 { get; set; }
public string MismatchCountAtA { get; set; }
public string MismatchCountAtB { get; set; }
public string MismatchCountAtDrb1 { get; set; }

/// <summary>
/// Empty constructor needed for reading results from files
/// </summary>
public WmdaConsensusResultsFile()
{
}

public WmdaConsensusResultsFile(string patientId, string donorId, ScoringResult result)
{
static int? CountMismatches(LocusSearchResult locusResult) => 2 - locusResult.MatchCount;
static string CountMismatches(LocusSearchResult locusResult) => $"{2 - locusResult.MatchCount}";

PatientId = patientId;
DonorId = donorId;
Expand All @@ -32,19 +39,19 @@ public override string ToString()

public class WmdaConsensusResultsFileSetTwo : WmdaConsensusResultsFile
{
public int AntigenMismatchCountAtA { get; set; }
public int AntigenMismatchCountAtB { get; set; }
public int AntigenMismatchCountAtDrb1 { get; set; }
public string AntigenMismatchCountAtA { get; set; }
public string AntigenMismatchCountAtB { get; set; }
public string AntigenMismatchCountAtDrb1 { get; set; }

public WmdaConsensusResultsFileSetTwo(string patientId, string donorId, ScoringResult result) : base(patientId, donorId, result)
{
static int CountAntigenMismatches(LocusSearchResult locusResult)
static string CountAntigenMismatches(LocusSearchResult locusResult)
{
return new List<bool?>
{
locusResult.ScoreDetailsAtPositionOne.IsAntigenMatch,
locusResult.ScoreDetailsAtPositionTwo.IsAntigenMatch
}.Count(x => x.HasValue && !x.Value);
}.Count(x => x.HasValue && !x.Value).ToString();
}

AntigenMismatchCountAtA = CountAntigenMismatches(result.SearchResultAtLocusA);
Expand Down
89 changes: 89 additions & 0 deletions Atlas.ManualTesting/Services/ConvertHlaRequester.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;
using Atlas.Common.Public.Models.GeneticData;
using Atlas.HlaMetadataDictionary.ExternalInterface.Models;
using Atlas.ManualTesting.Settings;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Polly;

namespace Atlas.ManualTesting.Services
{
/// <summary>
/// Copied <see cref="Atlas.MatchingAlgorithm.Functions.Models.Debug.HlaConversionRequest"/>,
/// instead of moving the model to the MatchingAlgorithm.Client.Models project,
/// to avoid breaking client/interface changes that would come from moving <see cref="Atlas.HlaMetadataDictionary.ExternalInterface.Models.TargetHlaCategory"/>.
/// </summary>
public class ConvertHlaRequest
{
public Locus Locus { get; set; }
public string HlaName { get; set; }
public TargetHlaCategory TargetHlaCategory { get; set; }

public override string ToString()
{
return $"convert {Locus},{HlaName} to {TargetHlaCategory}";
}
}

public interface IConvertHlaRequester
{
Task<IEnumerable<string>> ConvertHla(ConvertHlaRequest request);
}

internal class ConvertHlaRequester : IConvertHlaRequester
{
private const string FailedRequestPrefix = "Failed to";
private static readonly HttpClient HttpRequestClient = new();
private readonly HlaMetadataDictionarySettings hlaMetadataDictionarySettings;

public ConvertHlaRequester(IOptions<HlaMetadataDictionarySettings> settings)
{
hlaMetadataDictionarySettings = settings.Value;
}

public async Task<IEnumerable<string>> ConvertHla(ConvertHlaRequest request)
{
if (request?.HlaName is null)
{
throw new ArgumentException("ConvertHla request is missing required data.");
}

return await ExecuteHlaConversionRequest(request);
}

private async Task<IEnumerable<string>> ExecuteHlaConversionRequest(ConvertHlaRequest request)
{
var retryPolicy = Policy.Handle<Exception>().RetryAsync(10);

var requestResponse = await retryPolicy.ExecuteAndCaptureAsync(async () => await SendHlaConversionRequest(request));

return requestResponse.Outcome == OutcomeType.Successful
? requestResponse.Result
: new[] { $"{FailedRequestPrefix} {request}" };
}

private async Task<IReadOnlyCollection<string>> SendHlaConversionRequest(ConvertHlaRequest request)
{
try
{
var response = await HttpRequestClient.PostAsync(
hlaMetadataDictionarySettings.ConvertHlaRequestUrl, new StringContent(JsonConvert.SerializeObject(request)));
response.EnsureSuccessStatusCode();
var hlaConversionResult = JsonConvert.DeserializeObject<List<string>>(await response.Content.ReadAsStringAsync());

Debug.WriteLine($"Result received: {request}");

return hlaConversionResult;
}
catch (Exception ex)
{
Debug.WriteLine($"{FailedRequestPrefix} {request}. Details: {ex.Message}. Re-attempting until success or re-attempt count reached.");
throw;
}
}
}
}
Loading

0 comments on commit bbf42c6

Please sign in to comment.