Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Diagnostics;

using Eclipse.GraphQL;

using Microsoft.Extensions.Logging;
Expand All @@ -7,6 +9,7 @@

using SUI.Client.Core.Application.Interfaces;
using SUI.Client.Core.Application.Models;
using SUI.Client.Core.Application.UseCases.MatchPeople;
using SUI.Client.Core.Infrastructure.CsvParsers;

namespace SUI.Client.GraphQLProcessJob.Infrastructure;
Expand All @@ -20,84 +23,177 @@
{
public async Task RunAsync(CancellationToken cancellationToken)
{
var timer = Stopwatch.StartNew();
logger.LogInformation("Running Graph QL Process Job.");

Check warning on line 27 in src/SUI.Client/SUI.Client.GraphQLProcessJob/Infrastructure/GraphQLProcessor.cs

View workflow job for this annotation

GitHub Actions / build

Reduce the number of Information logging calls within this code block from 4 to the 2 allowed.

Check warning on line 27 in src/SUI.Client/SUI.Client.GraphQLProcessJob/Infrastructure/GraphQLProcessor.cs

View workflow job for this annotation

GitHub Actions / build

Reduce the number of Information logging calls within this code block from 4 to the 2 allowed.
var mappings = csvMatchDataOptions.Value.ColumnMappings;

List<CsvRecordDto> csvRecords = await FetchAndCompilePersonRecordsAsync(mappings, cancellationToken);

logger.LogInformation("Completed compiling GraphQL records. Total records retrieved: {Count}. Elapsed Time: {ElapsedTime}",
csvRecords.Count, timer.Elapsed.ToString("g"));

Check warning on line 33 in src/SUI.Client/SUI.Client.GraphQLProcessJob/Infrastructure/GraphQLProcessor.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=DFE-Digital_SUI_Matcher&issues=AZ9lgx96Z2lD7IoPtSEf&open=AZ9lgx96Z2lD7IoPtSEf&pullRequest=370

var matchedResults = await matchPersonRecordOrchestrator.ProcessAsync(
csvRecords,
"graphql_extract",
cancellationToken
);

logger.LogInformation(
"Finished processing matching. Result count: {Count}. Matches: {MatchCount}. Elapsed time: {ElapsedTime}",
matchedResults.Count, matchedResults.Count(x => x.ApiResult is
{
Result.IsHighConfidenceMatch: true
}), timer.Elapsed.ToString("g"));

await SaveMatchedNhsNumbersAsync(matchedResults, mappings, cancellationToken);

logger.LogInformation("GraphQL Job Complete. Total time: {ElapsedTime}", timer.Elapsed.ToString("g"));

Check warning on line 50 in src/SUI.Client/SUI.Client.GraphQLProcessJob/Infrastructure/GraphQLProcessor.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=DFE-Digital_SUI_Matcher&issues=AZ9lgx96Z2lD7IoPtSEg&open=AZ9lgx96Z2lD7IoPtSEg&pullRequest=370
}

private async Task<List<CsvRecordDto>> FetchAndCompilePersonRecordsAsync(
CsvMatchDataOptions.Headers mappings,
CancellationToken cancellationToken)
{
int pageNumber = 1;
const int pageSize = 10;
bool hasMoreResults = true;
const int pageSize = 100;
var csvRecords = new List<CsvRecordDto>();
var mappings = csvMatchDataOptions.Value.ColumnMappings;

while (hasMoreResults && !cancellationToken.IsCancellationRequested)
while (!cancellationToken.IsCancellationRequested)
{
var results = await eclipseClient.PersonByCriteria.ExecuteAsync(options.Value.MaxAge,
new RequestCursorInput { PageNumber = pageNumber, PageSize = pageSize }, cancellationToken);
results.EnsureNoErrors();

if (results.Data?.PersonByCriteria?.Results is { Count: > 0 } resultsList)
var resultsList = results.Data?.PersonByCriteria?.Results;
if (resultsList == null || resultsList.Count == 0)
{
foreach (var result in resultsList)
{
if (result is not IPersonByCriteria_PersonByCriteria_Results_Person person)
{
continue;
}

var personDictionary = new Dictionary<string, string>
{
{ mappings.Id, person.Id },
{ mappings.Given, person.Forename ?? "" },
{ mappings.Family, person.Surname ?? "" },
{ mappings.BirthDate, person.DateOfBirth?.Lower?.ToString(csvMatchDataOptions.Value.DateFormat) ?? "" },
{
mappings.Postcode,
person.Addresses.FirstOrDefault(a => a.Id == person.PreferredAddress?.Id)?.Location
?.Postcode ?? ""
}
};

if (!string.IsNullOrEmpty(mappings.NhsNumber))
{
personDictionary[mappings.NhsNumber] = person.NhsNumber ?? "";
}

if (!string.IsNullOrEmpty(mappings.Gender))
{
personDictionary[mappings.Gender] = person.Gender?.ToString().ToLower() ?? "";
}

csvRecords.Add(new CsvRecordDto(personDictionary));
}
break;
}

var cursor = results.Data?.PersonByCriteria?.Cursor;
if (cursor != null && cursor.Offset + cursor.Returned < cursor.TotalSize)
{
pageNumber++;
}
else
foreach (var result in resultsList)
{
if (result is IPersonByCriteria_PersonByCriteria_Results_Person person)
{
hasMoreResults = false;
csvRecords.Add(new CsvRecordDto(MapPersonToDictionary(person, mappings)));
}
}
else

var cursor = results.Data?.PersonByCriteria?.Cursor;
if (cursor != null && logger.IsEnabled(LogLevel.Information))
{
hasMoreResults = false;
logger.LogInformation(
"Processed Page: {Page}. Page size: {PageSize}. Total Records: {TotalRecords}",
cursor.PageNumber, cursor.PageSize, cursor.TotalSize);
}

if (cursor == null || cursor.Offset + cursor.Returned >= cursor.TotalSize)
{
break;
}

pageNumber++;
}

logger.LogInformation("Completed compiling GraphQL records. Total records retrieved: {Count}.",
csvRecords.Count);
return csvRecords;
}

var matchedResults = await matchPersonRecordOrchestrator.ProcessAsync(
csvRecords,
"graphql_extract",
cancellationToken
);
private Dictionary<string, string> MapPersonToDictionary(
IPersonByCriteria_PersonByCriteria_Results_Person person,
CsvMatchDataOptions.Headers mappings)
{
var personDictionary = new Dictionary<string, string>
{
{ mappings.Id, person.Id },
{ mappings.Given, person.Forename ?? "" },
{ mappings.Family, person.Surname ?? "" },
{ mappings.BirthDate, person.DateOfBirth?.Lower?.ToString(csvMatchDataOptions.Value.DateFormat) ?? "" },
{ mappings.Postcode, GetPreferredPostcode(person) },
{ "__ObjectVersion", person.ObjectVersion.ToString() },
{ "__PersonTypes", string.Join(",", person.PersonTypes ?? []) }
};

logger.LogInformation(
"Finished processing matching with orchestrator. Result count: {Count}. Matches: {MatchCount}",
matchedResults.Count, matchedResults.Count(x => x.ApiResult is
if (!string.IsNullOrEmpty(mappings.NhsNumber))
{
personDictionary[mappings.NhsNumber] = person.NhsNumber ?? "";
}

if (!string.IsNullOrEmpty(mappings.Gender))
{
personDictionary[mappings.Gender] = person.Gender?.ToString().ToLower() ?? "";
}

return personDictionary;
}

private static string GetPreferredPostcode(IPersonByCriteria_PersonByCriteria_Results_Person person) =>
person.Addresses.FirstOrDefault(a => a.Id == person.PreferredAddress?.Id)?.Location?.Postcode ?? "";

private async Task SaveMatchedNhsNumbersAsync(

Check warning on line 131 in src/SUI.Client/SUI.Client.GraphQLProcessJob/Infrastructure/GraphQLProcessor.cs

View workflow job for this annotation

GitHub Actions / build

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.

Check warning on line 131 in src/SUI.Client/SUI.Client.GraphQLProcessJob/Infrastructure/GraphQLProcessor.cs

View workflow job for this annotation

GitHub Actions / build

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.

Check failure on line 131 in src/SUI.Client/SUI.Client.GraphQLProcessJob/Infrastructure/GraphQLProcessor.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=DFE-Digital_SUI_Matcher&issues=AZ9mAg1dNq71T6l0mysg&open=AZ9mAg1dNq71T6l0mysg&pullRequest=370
IEnumerable<ProcessedMatchRecord<CsvRecordDto>> matchedResults,
CsvMatchDataOptions.Headers mappings,
CancellationToken cancellationToken)
{
foreach (var result in matchedResults)
{
if (result.ApiResult is not { Result.IsHighConfidenceMatch: true } ||
string.IsNullOrEmpty(result.ApiResult.Result.NhsNumber))
{
Result.IsHighConfidenceMatch: true
}));
continue;
}

var personId = result.OriginalData.Record[mappings.Id];
var matchedNhsNumber = result.ApiResult.Result.NhsNumber;

var existingNhsNumber = !string.IsNullOrEmpty(mappings.NhsNumber) &&
result.OriginalData.Record.TryGetValue(mappings.NhsNumber, out var extNhs)
? extNhs
: null;

if (!string.IsNullOrEmpty(existingNhsNumber))
{
logger.LogInformation("Person {PersonId} already has NHS number. Skipping update.",
personId);

Check warning on line 155 in src/SUI.Client/SUI.Client.GraphQLProcessJob/Infrastructure/GraphQLProcessor.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=DFE-Digital_SUI_Matcher&issues=AZ9mAg1dNq71T6l0mysh&open=AZ9mAg1dNq71T6l0mysh&pullRequest=370
continue;
}

if (!result.OriginalData.Record.TryGetValue("__ObjectVersion", out var objVerStr) ||
!int.TryParse(objVerStr, out var objectVersion))
{
logger.LogWarning("Could not find ObjectVersion for Person {PersonId}. Skipping NHS number update.", personId);
continue;
}

logger.LogInformation("Saving matched NHS number {NhsNumber} for Person {PersonId} with ObjectVersion {ObjectVersion}.",
matchedNhsNumber, personId, objectVersion);

Check warning on line 167 in src/SUI.Client/SUI.Client.GraphQLProcessJob/Infrastructure/GraphQLProcessor.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=DFE-Digital_SUI_Matcher&issues=AZ9f7Dr5G8KTb5wKr8y7&open=AZ9f7Dr5G8KTb5wKr8y7&pullRequest=370

try
{
var personTypesList = new List<PersonType>();
if (result.OriginalData.Record.TryGetValue("__PersonTypes", out var typesStr) &&
!string.IsNullOrEmpty(typesStr))
{
personTypesList = typesStr.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(Enum.Parse<PersonType>)
.ToList();
}

var updateInput = new UpdatePerson
{
Id = personId,
NhsNumber = matchedNhsNumber,
ObjectVersion = objectVersion,
PersonTypes = personTypesList
};

var updateResult = await eclipseClient.UpdatePerson.ExecuteAsync(updateInput, cancellationToken);
updateResult.EnsureNoErrors();

logger.LogInformation("Successfully saved NHS number for Person {PersonId}.", personId);

Check warning on line 191 in src/SUI.Client/SUI.Client.GraphQLProcessJob/Infrastructure/GraphQLProcessor.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=DFE-Digital_SUI_Matcher&issues=AZ9f7Dr5G8KTb5wKr8y8&open=AZ9f7Dr5G8KTb5wKr8y8&pullRequest=370
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to save NHS number for Person {PersonId}.", personId);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ query PersonByCriteria($maxAge: Int, $paging: RequestCursorInput) {
mask
}
id
objectVersion
personTypes
Comment thread
jtaylor100 marked this conversation as resolved.
forename
surname
gender
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mutation UpdatePerson($input: UpdatePerson!) {
updatePerson(input: $input) {
id
objectVersion
nhsNumber
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
"Microsoft.Hosting.Lifetime": "Information",
"System.Net.Http.HttpClient": "Error"
}
},
"GraphQlProcessJob": {
Expand Down
Loading
Loading