Skip to content

Commit

Permalink
chore: #1294: Detect subjects with missing HLA.
Browse files Browse the repository at this point in the history
  • Loading branch information
zabeen committed Apr 30, 2024
1 parent 4bec26f commit a92547c
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Atlas.MatchPrediction.Test.Validation.Data.Repositories.Homework
public interface IHomeworkSetRepository
{
Task<int> Add(string setName, string resultsPath, string matchLoci);
Task<HomeworkSet> Get(int setId);
}

public class HomeworkSetRepository : IHomeworkSetRepository
Expand Down Expand Up @@ -37,5 +38,15 @@ await using (var connection = new SqlConnection(connectionString))
return (await connection.QueryAsync<int>(sql, new { setName, resultsPath, matchLoci })).Single();
}
}

public async Task<HomeworkSet> Get(int setId)
{
const string sql = $@" SELECT * FROM HomeworkSets WHERE Id = @{nameof(setId)}";

await using (var connection = new SqlConnection(connectionString))
{
return connection.QuerySingleOrDefault<HomeworkSet>(sql, new { setId });
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
using Atlas.Common.Sql.BulkInsert;
using Atlas.MatchPrediction.Test.Validation.Data.Context;
using Atlas.MatchPrediction.Test.Validation.Data.Models.Homework;
using Dapper;
using Microsoft.Data.SqlClient;

namespace Atlas.MatchPrediction.Test.Validation.Data.Repositories.Homework
{
public interface IPatientDonorPairRepository : IBulkInsertRepository<PatientDonorPair>
{
Task<IEnumerable<PatientDonorPair>> GetUnprocessedPairs(int homeworkSetId);
Task UpdateEditableFields(PatientDonorPair pdp);
}

public class PatientDonorPairRepository : BulkInsertRepository<PatientDonorPair>, IPatientDonorPairRepository
Expand All @@ -14,5 +18,38 @@ public PatientDonorPairRepository(string connectionString)
: base(connectionString, nameof(MatchPredictionValidationContext.PatientDonorPairs))
{
}

/// <inheritdoc />
public async Task<IEnumerable<PatientDonorPair>> GetUnprocessedPairs(int homeworkSetId)
{
const string sql = $@"
SELECT *
FROM PatientDonorPairs
WHERE {nameof(PatientDonorPair.HomeworkSet_Id)} = @{nameof(homeworkSetId)} AND {nameof(PatientDonorPair.IsProcessed)} = 0";

await using (var connection = new SqlConnection(ConnectionString))
{
return await connection.QueryAsync<PatientDonorPair>(sql, new { homeworkSetId });
}
}

/// <inheritdoc />
public async Task UpdateEditableFields(PatientDonorPair pdp)
{
const string sql = $@"
UPDATE PatientDonorPairs SET
IsProcessed = @{nameof(pdp.IsProcessed)},
DidPatientHaveMissingHla = @{nameof(pdp.DidPatientHaveMissingHla)},
DidDonorHaveMissingHla = @{nameof(pdp.DidDonorHaveMissingHla)},
PatientImputationCompleted = @{nameof(pdp.PatientImputationCompleted)},
DonorImputationCompleted = @{nameof(pdp.DonorImputationCompleted)},
MatchingGenotypesCalculated = @{nameof(pdp.MatchingGenotypesCalculated)}
WHERE Id = @{nameof(pdp.Id)}";

await using (var connection = new SqlConnection(ConnectionString))
{
await connection.ExecuteAsync(sql, pdp);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public interface ISubjectRepository : IBulkInsertRepository<SubjectInfo>
{
Task<IEnumerable<SubjectInfo>> GetPatients(int firstPatientId = 0);
Task<IEnumerable<SubjectInfo>> GetDonors();
Task<SubjectInfo?> GetByExternalId(string externalId);
}

public class SubjectRepository : BulkInsertRepository<SubjectInfo>, ISubjectRepository
Expand Down Expand Up @@ -43,6 +44,17 @@ public async Task<IEnumerable<SubjectInfo>> GetDonors()
return await GetAllSubjects(SubjectType.Donor);
}

/// <inheritdoc />
public async Task<SubjectInfo?> GetByExternalId(string externalId)
{
const string sql = @$"SELECT * FROM {TableName} WHERE {nameof(SubjectInfo.ExternalId)} = @{nameof(externalId)}";

await using (var conn = new SqlConnection(ConnectionString))
{
return await conn.QuerySingleOrDefaultAsync<SubjectInfo>(sql, new { externalId });
}
}

private async Task<IEnumerable<SubjectInfo>> GetAllSubjects(SubjectType subjectType)
{
var param = subjectType.ToString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ private static void RegisterDatabaseServices(this IServiceCollection services, F
new PatientDonorPairRepository(fetchSqlConnectionString(sp)));

services.AddScoped<IHomeworkRequestProcessor, HomeworkRequestProcessor>();
services.AddScoped<IPatientDonorPairProcessor, PatientDonorPairProcessor>();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
using Atlas.Common.Public.Models.GeneticData;
using System;
using Atlas.Common.Public.Models.GeneticData;
using System.Collections.Generic;
using System.Linq;
using Atlas.Common.Public.Models.GeneticData.PhenotypeInfo;
using Atlas.Common.Public.Models.GeneticData.PhenotypeInfo.TransferModels;

namespace Atlas.MatchPrediction.Test.Validation.Models
{
internal static class MatchLociExtensions
{
private const string MatchLociSeparator = ",";

public static string MatchLociToString(this LociInfoTransfer<bool> matchLociInfo)
{
var matchLoci = matchLociInfo.ToLociInfo().Reduce(
Expand All @@ -16,7 +21,16 @@ public static string MatchLociToString(this LociInfoTransfer<bool> matchLociInfo
},
new List<Locus>());

return string.Join(",", matchLoci);
return string.Join(MatchLociSeparator, matchLoci);
}

public static LociInfo<bool> ToLociInfo(this string matchLoci)
{
return matchLoci
.Split(MatchLociSeparator)
.Aggregate(
new LociInfo<bool>(false),
(currentLociInfo, locus) => currentLociInfo.SetLocus(Enum.Parse<Locus>(locus), true));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Atlas.Common.Utils.Extensions;
using Atlas.MatchPrediction.Test.Validation.Data.Models.Homework;
using Atlas.MatchPrediction.Test.Validation.Data.Repositories.Homework;
using Atlas.MatchPrediction.Test.Validation.Models;
Expand All @@ -19,13 +20,16 @@ internal class HomeworkRequestProcessor : IHomeworkRequestProcessor
{
private readonly IHomeworkSetRepository setRepository;
private readonly IPatientDonorPairRepository pdpRepository;
private readonly IPatientDonorPairProcessor pdpProcessor;

public HomeworkRequestProcessor(
IHomeworkSetRepository setRepository,
IPatientDonorPairRepository pdpRepository)
IPatientDonorPairRepository pdpRepository,
IPatientDonorPairProcessor pdpProcessor)
{
this.setRepository = setRepository;
this.pdpRepository = pdpRepository;
this.pdpProcessor = pdpProcessor;
}

/// <inheritdoc />
Expand All @@ -36,9 +40,29 @@ public async Task<int> StoreHomeworkRequest(HomeworkRequest request)
return setId;
}

public Task StartOrContinueHomeworkRequest(int homeworkSetId)
public async Task StartOrContinueHomeworkRequest(int homeworkSetId)
{
throw new NotImplementedException();
var set = await setRepository.Get(homeworkSetId);

if (set == null)
{
throw new ArgumentException($"No homework set found with id {homeworkSetId}.");
}

var pdps = (await pdpRepository.GetUnprocessedPairs(homeworkSetId)).ToList();

if (pdps.IsNullOrEmpty())
{
System.Diagnostics.Debug.WriteLine($"No unprocessed patient-donor pairs found for homework set {homeworkSetId}.");
return;
}

var matchLoci = set.MatchLoci.ToLociInfo();

foreach (var pdp in pdps)
{
await pdpProcessor.Process(pdp, matchLoci);
}
}

private static PatientDonorPair MapToDatabaseModel(string patientDonorPair, int homeworkSetId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System.Threading.Tasks;
using Atlas.Common.Public.Models.GeneticData.PhenotypeInfo;
using Atlas.MatchPrediction.Test.Validation.Data.Models;
using Atlas.MatchPrediction.Test.Validation.Data.Models.Homework;
using Atlas.MatchPrediction.Test.Validation.Data.Repositories;
using Atlas.MatchPrediction.Test.Validation.Data.Repositories.Homework;

namespace Atlas.MatchPrediction.Test.Validation.Services.Exercise4.Homework
{
public interface IPatientDonorPairProcessor
{
Task Process(PatientDonorPair pdp, LociInfo<bool> matchLoci);
}

internal class PatientDonorPairProcessor : IPatientDonorPairProcessor
{
private readonly ISubjectRepository subjectRepository;
private readonly IPatientDonorPairRepository pdpRepository;

public PatientDonorPairProcessor(
ISubjectRepository subjectRepository,
IPatientDonorPairRepository pdpRepository)
{
this.pdpRepository = pdpRepository;
this.subjectRepository = subjectRepository;
}

/// <inheritdoc />
public async Task Process(PatientDonorPair pdp, LociInfo<bool> matchLoci)
{
if (await PatientHasMissingHla(pdp, matchLoci)) return;

if (await DonorHasMissingHla(pdp, matchLoci)) return;

// Else submit patient imputation request

// Then submit donor imputation request

// Then submit matching genotypes request
}

private async Task<bool> PatientHasMissingHla(PatientDonorPair pdp, LociInfo<bool> matchLoci)
{
var patientHasMissingHla = !await HasAllRequiredLoci(pdp.PatientId, false, matchLoci);

// ReSharper disable once InvertIf
if (patientHasMissingHla)
{
pdp.DidPatientHaveMissingHla = true;
pdp.IsProcessed = true;
await UpdatePatientDonorPairRecord(pdp);
}

return patientHasMissingHla;
}

private async Task<bool> DonorHasMissingHla(PatientDonorPair pdp, LociInfo<bool> matchLoci)
{
var donorHasMissingHla = !await HasAllRequiredLoci(pdp.DonorId, true, matchLoci);

// ReSharper disable once InvertIf
if (donorHasMissingHla)
{
pdp.DidDonorHaveMissingHla = true;
pdp.IsProcessed = true;
await UpdatePatientDonorPairRecord(pdp);
}

return donorHasMissingHla;
}

/// <returns>
/// Will return false if either the subject is missing required HLA,
/// or if the subject does not exist, as this suggests it was not imported due to missing required HLA.
/// </returns>
private async Task<bool> HasAllRequiredLoci(string externalId, bool isDonor, LociInfo<bool> matchLoci)
{
var subject = await subjectRepository.GetByExternalId(externalId);

if (subject == null)
{
return false;
}

// Can assume that if donor is in the db, it has all the required loci
if (isDonor)
{
return true;
}

// patient must be typed at all match loci
var patientHla = subject.ToPhenotypeInfo();
return matchLoci.AllAtLoci((locus, isRequired) => !isRequired || patientHla.GetLocus(locus).Position1And2NotNull());
}

private async Task UpdatePatientDonorPairRecord(PatientDonorPair pdp)
{
await pdpRepository.UpdateEditableFields(pdp);
}
}
}

0 comments on commit a92547c

Please sign in to comment.