public async Task <PostcodeDto> GetPostcodeAsync(string postcode, CancellationToken cancellationToken) { IEnumerable <PostcodeDto> postcodes = await GetPostcodesAsync(new List <string>() { postcode }, cancellationToken); PostcodeDto postcodeDto = postcodes.FirstOrDefault(); return(postcodeDto); }
public async Task <GetPostcodeResponse> Handle(GetPostcodeRequest request, CancellationToken cancellationToken) { request.Postcode = PostcodeFormatter.FormatPostcode(request.Postcode); PostcodeDto postcodeDto = await _postcodeAndAddressGetter.GetPostcodeAsync(request.Postcode, cancellationToken); GetPostcodeResponse getNearbyGetPostcodesResponse = _mapper.Map <PostcodeDto, GetPostcodeResponse>(postcodeDto); getNearbyGetPostcodesResponse.AddressDetails = _addressDetailsSorter.OrderAddressDetailsResponse(getNearbyGetPostcodesResponse.AddressDetails); return(getNearbyGetPostcodesResponse); }
public async Task GetPostcodeAsync() { CancellationToken cancellationToken = new CancellationToken(); PostcodeAndAddressGetter postcodeAndAddressGetter = new PostcodeAndAddressGetter(_repository.Object, _qasAddressGetter.Object, _postcodesWithoutAddressesCache.Object); PostcodeDto result = await postcodeAndAddressGetter.GetPostcodeAsync("NG11AA", cancellationToken); _repository.Verify(x => x.GetPostcodesAsync(It.Is <IEnumerable <string> >(y => y.Count() == 1 && y.Contains("NG1 1AA"))), Times.Once); Assert.AreEqual(1, result.AddressDetails.Count()); Assert.AreEqual("1_addressline1", result.AddressDetails.FirstOrDefault().AddressLine1); Assert.AreEqual("1_addressline2", result.AddressDetails.FirstOrDefault().AddressLine2); Assert.AreEqual("1_addressline1", result.AddressDetails.FirstOrDefault().AddressLine3); Assert.AreEqual("1_locality", result.AddressDetails.FirstOrDefault().Locality); }
public PostcodeDto MapToPostcodeDto(string postcode, IEnumerable <QasFormatRootResponse> qasFormatRootResponses) { DateTime timeNow = DateTime.UtcNow; postcode = PostcodeFormatter.FormatPostcode(postcode); PostcodeDto postcodeDto = new PostcodeDto(); postcodeDto.Postcode = postcode; postcodeDto.LastUpdated = timeNow; foreach (var qasFormatRootResponse in qasFormatRootResponses) { AddressDetailsDto addressDetailsDto = new AddressDetailsDto(); foreach (QasFormatAddressReponse address in qasFormatRootResponse.Address) // to deal with strange way results are returned ... { if (!String.IsNullOrWhiteSpace(address.AddressLine1)) { addressDetailsDto.AddressLine1 = address.AddressLine1; } else if (!String.IsNullOrWhiteSpace(address.AddressLine2)) { addressDetailsDto.AddressLine2 = address.AddressLine2; } else if (!String.IsNullOrWhiteSpace(address.AddressLine3)) { addressDetailsDto.AddressLine3 = address.AddressLine3; } else if (!String.IsNullOrWhiteSpace(address.Locality)) { addressDetailsDto.Locality = address.Locality; } else if (!String.IsNullOrWhiteSpace(address.PostalCode)) { addressDetailsDto.Postcode = PostcodeFormatter.FormatPostcode(address.PostalCode); } } // filter out postcodes that weren't returned or don't have the expected postcode if (!String.IsNullOrWhiteSpace(addressDetailsDto.Postcode) && addressDetailsDto.Postcode == postcode) { addressDetailsDto.LastUpdated = timeNow; postcodeDto.AddressDetails.Add(addressDetailsDto); } } return(postcodeDto); }
public void SetUp() { _repository = new Mock <IRepository>(); _repository.SetupAllProperties(); _qasService = new Mock <IQasService>(); _qasService.SetupAllProperties(); _qasMapper = new Mock <IQasMapper>(); ILookup <string, string> missingQasFormatIdsGroupedByPostCode = new List <KeyValuePair <string, string> >() { new KeyValuePair <string, string>("ng15ba", "AC4705C7-E358-4338-A4FC-DE5EA241B5F1") }.ToLookup(x => x.Key, x => x.Value); _qasMapper.Setup(x => x.GetFormatIds(It.IsAny <IEnumerable <QasSearchRootResponse> >())).Returns(missingQasFormatIdsGroupedByPostCode); _missingPostcodeDtosFromQas = new PostcodeDto() { Postcode = "NG1 6DQ", AddressDetails = new List <AddressDetailsDto>() { new AddressDetailsDto() { AddressLine1 = "2_addressline1", AddressLine2 = "2_addressline2", AddressLine3 = "2_addressline1", Locality = "2_locality" } } }; _qasMapper.Setup(x => x.MapToPostcodeDto(It.IsAny <string>(), It.IsAny <IEnumerable <QasFormatRootResponse> >())).Returns(_missingPostcodeDtosFromQas); _friendlyNameGenerator = new Mock <IFriendlyNameGenerator>(); _friendlyNameGenerator.Setup(x => x.GenerateFriendlyName(It.IsAny <PostcodeDto>())); _logger = new Mock <ILoggerWrapper <QasAddressGetter> >(); _logger.SetupAllProperties(); }
public void MapToPostcodeDto() { QasMapper qasMapper = new QasMapper(); var postCode = "ng1 5fs"; var expectedPostcode = PostcodeFormatter.FormatPostcode(postCode); IEnumerable <QasFormatRootResponse> qasFormatRootResponses = new List <QasFormatRootResponse>() { new QasFormatRootResponse() { Address = new List <QasFormatAddressReponse>() { new QasFormatAddressReponse() { PostalCode = postCode }, new QasFormatAddressReponse() { AddressLine1 = "line1" }, new QasFormatAddressReponse() { AddressLine2 = "line2" }, new QasFormatAddressReponse() { AddressLine3 = "line3" }, new QasFormatAddressReponse() { Locality = "loc" }, } }, new QasFormatRootResponse() { Address = new List <QasFormatAddressReponse>() { new QasFormatAddressReponse() { PostalCode = "FilterMeOut" }, new QasFormatAddressReponse() { AddressLine1 = "line1" }, new QasFormatAddressReponse() { AddressLine2 = "line2" }, new QasFormatAddressReponse() { AddressLine3 = "line3" }, new QasFormatAddressReponse() { Locality = "loc" } } }, new QasFormatRootResponse() { Address = new List <QasFormatAddressReponse>() { new QasFormatAddressReponse() { PostalCode = null }, new QasFormatAddressReponse() { AddressLine1 = "line1" }, new QasFormatAddressReponse() { AddressLine2 = "line2" }, new QasFormatAddressReponse() { AddressLine3 = "line3" }, new QasFormatAddressReponse() { Locality = "loc" } } } }; PostcodeDto result = qasMapper.MapToPostcodeDto(postCode, qasFormatRootResponses); Assert.AreEqual(1, result.AddressDetails.Count); Assert.AreEqual(expectedPostcode, result.Postcode); Assert.AreNotEqual(DateTime.MinValue, result.LastUpdated); AddressDetailsDto addressResult = result.AddressDetails.FirstOrDefault(); Assert.AreEqual(expectedPostcode, addressResult.Postcode); Assert.AreEqual("line1", addressResult.AddressLine1); Assert.AreEqual("line2", addressResult.AddressLine2); Assert.AreEqual("line3", addressResult.AddressLine3); Assert.AreEqual("loc", addressResult.Locality); Assert.AreNotEqual(DateTime.MinValue, addressResult.LastUpdated); }
public async Task <IEnumerable <PostcodeDto> > GetPostCodesAndAddressesFromQasAsync(IEnumerable <string> missingPostcodes, CancellationToken cancellationToken) { // call QAS for missing postcodes and addresses List <Task <QasSearchRootResponse> > qasSearchResponseTasks = new List <Task <QasSearchRootResponse> >(); List <QasSearchRootResponse> qasSearchResponses = new List <QasSearchRootResponse>(); foreach (string missingPostcode in missingPostcodes) { Task <QasSearchRootResponse> qasResponseTask = _qasService.GetGlobalIntuitiveSearchResponseAsync(PostcodeFormatter.FormatPostcode(missingPostcode), cancellationToken); qasSearchResponseTasks.Add(qasResponseTask); } while (qasSearchResponseTasks.Count > 0) { Task <QasSearchRootResponse> finishedQasResponseTask = await Task.WhenAny(qasSearchResponseTasks); qasSearchResponseTasks.Remove(finishedQasResponseTask); QasSearchRootResponse qasSearchResponse = await finishedQasResponseTask; qasSearchResponses.Add(qasSearchResponse); } // call QAS for address details (grouped by postcode to avoid sending 1000s of request at once and to map a single PostcodeDto at a time) ILookup <string, string> missingQasFormatIdsGroupedByPostCode = _qasMapper.GetFormatIds(qasSearchResponses); List <PostcodeDto> missingPostcodeDtos = new List <PostcodeDto>(); foreach (IGrouping <string, string> missingQasFormatIds in missingQasFormatIdsGroupedByPostCode) { List <Task <QasFormatRootResponse> > qasFormatResponseTasks = new List <Task <QasFormatRootResponse> >(); foreach (string missingQasFormatId in missingQasFormatIds) { Task <QasFormatRootResponse> qasFormatResponseTask = _qasService.GetGlobalIntuitiveFormatResponseAsync(missingQasFormatId, cancellationToken); qasFormatResponseTasks.Add(qasFormatResponseTask); } List <QasFormatRootResponse> qasFormatResponses = new List <QasFormatRootResponse>(); while (qasFormatResponseTasks.Count > 0) { Task <QasFormatRootResponse> finishedQasFormatResponseTask = await Task.WhenAny(qasFormatResponseTasks); qasFormatResponseTasks.Remove(finishedQasFormatResponseTask); QasFormatRootResponse qasFormatResponse = await finishedQasFormatResponseTask; qasFormatResponses.Add(qasFormatResponse); } PostcodeDto missingPostcodeDtosForThisBatch = _qasMapper.MapToPostcodeDto(missingQasFormatIds.Key, qasFormatResponses); try { //new addresses need a friendly name _friendlyNameGenerator.GenerateFriendlyName(missingPostcodeDtosForThisBatch); } catch (Exception ex) { _logger.LogWarning("Error generating friendly name", ex); } missingPostcodeDtos.Add(missingPostcodeDtosForThisBatch); } if (missingPostcodeDtos.Any()) { await _repository.SaveAddressesAndFriendlyNameAsync(missingPostcodeDtos); } return(missingPostcodeDtos); }
/// <summary> /// Adds a friendly name to PostcodeDtos based on address details within the postcode /// This rapidly became a mess, I'm sorry :( /// </summary> /// <param name="postcodeDtos"></param> public void GenerateFriendlyName(PostcodeDto postcodeDto) { IEnumerable <AddressDetailsDto> details = postcodeDto.AddressDetails.Where(x => !string.IsNullOrWhiteSpace(x.AddressLine1)); if (details == null || !details.Any()) { postcodeDto.FriendlyName = ""; return; //may want a different default but seems better to resort to postcode only in unhandleable cases } int totalAddresses = details.Count(); //Identify and return simple addresses double simplePostcodeFraction = (double)details.Where(x => string.IsNullOrWhiteSpace(x.AddressLine2)).Count() / totalAddresses; //Simple Address Block if (simplePostcodeFraction >= ThresholdForMost) { //From a test sample seems to happen fairly frequently //filtering down to only only entries in the "dominant" case // this should clear out the occasional building name / business in a residential postcode IEnumerable <string> firstLine = details.Where(x => !string.IsNullOrWhiteSpace(x.AddressLine1)) .Select(x => x.AddressLine1); (string name, int count) = firstLine.RemoveNumbersAndSelectMostCommon(); if ((double)count / totalAddresses >= ThresholdForMost) { string numberRange = firstLine.ExtractNumbers(name) .GenerateNumbersDescriptor(5); postcodeDto.FriendlyName = numberRange + name; return; } } //simple flat block //doesn't seem to be another way to identify a block of flats easily double simpleFlatFraction = (double)details.Where(x => x.AddressLine1.Contains("Flat") || x.AddressLine1.Contains("Apartment") || x.AddressLine1.All(char.IsDigit) ).Count() / totalAddresses; if (simpleFlatFraction >= ThresholdForMost) { string result = details.Where(x => !string.IsNullOrWhiteSpace(x.AddressLine2)) .Select(y => y.AddressLine2) .GroupBy(z => z) .OrderByDescending(a => a.Count()) .First() .Key; // see if we can add a street name too double hasThirdLineFraction = (double)details.Where(x => !string.IsNullOrWhiteSpace(x.AddressLine3)) .Count() / totalAddresses; if (hasThirdLineFraction > ThresholdForMost) { (string nameLine3, int countLine3) = details.Where(x => !string.IsNullOrWhiteSpace(x.AddressLine3)) .Select(y => y.AddressLine3) .RemoveNumbersAndSelectMostCommon(); if ((double)countLine3 / totalAddresses > ThresholdForMost) { result = string.Concat(result, ", ", nameLine3); } } postcodeDto.FriendlyName = result; return; } //case is neither a simple flat nor a simple street address - now it gets more difficult //most likely scenario is "# street name, district" //Hence move through each address line at a time, stripping out numbers and checking if the count meets the tolerance criteria //hopefully all of the flats have been caught by this out, and non-residential addresses should only match on street name IEnumerable <string> addressLine1 = details.Where(x => !string.IsNullOrWhiteSpace(x.AddressLine1)) .Select(y => y.AddressLine1); (string line1Name, int line1Count) = addressLine1.RemoveNumbersAndSelectMostCommon(); if ((double)line1Count / totalAddresses > ThresholdForMost) { string numberRange = addressLine1.ExtractNumbers(line1Name) .GenerateNumbersDescriptor(5); //probably of the farm "# street name, region, locality" postcodeDto.FriendlyName = numberRange + line1Name; return; } IEnumerable <string> addressLine2 = details.Where(x => !string.IsNullOrWhiteSpace(x.AddressLine2)) .Select(y => y.AddressLine2); (string line2Name, int line2Count) = addressLine2.RemoveNumbersAndSelectMostCommon(); if ((double)line2Count / totalAddresses > ThresholdForMost) { string numberRange = addressLine2.ExtractNumbers(line2Name) .GenerateNumbersDescriptor(5); //probably non residential addresses of the form "business name, # street name" postcodeDto.FriendlyName = numberRange + line2Name; return; } IEnumerable <string> addressLine3 = details.Where(x => !string.IsNullOrWhiteSpace(x.AddressLine3)) .Select(y => y.AddressLine3); (string line3Name, int line3Count) = addressLine3.RemoveNumbersAndSelectMostCommon(); if ((double)line3Count / totalAddresses > ThresholdForMost) { string numberRange = addressLine3.ExtractNumbers(line3Name) .GenerateNumbersDescriptor(5); //weird edge cases with building names and buisness names on line 1 and 2 //should pull streets out of line 3 postcodeDto.FriendlyName = numberRange + line3Name; return; } //Everything has failed :( postcodeDto.FriendlyName = ""; return; }
public void Setup() { _postcodeDto = new PostcodeDto(); }