Example #1
0
        private List <ScoreRecord> CompareActualWithExpectedResults(Document actual, DocumentCheckRequest expected, ILogger log)
        {
            int documentPoints = 0;

            var results = new List <ScoreRecord>();

            log.LogTrace($"Checking accuracy of document {actual.FileName} header");

            // There are 7 header fields to check.  We will identify those that match.
            Dictionary <string, bool> matches = new Dictionary <string, bool> {
                { "Account", false }, { "GrandTotal", false }, { "ShippingTotal", false }, { "NetTotal", false }, { "VatAmount", false }, { "PostCode", false }, { "TaxDate", false }
            };

            matches["Account"]       = Compare <string>(actual.FileName, "Account", actual.Account, expected.Account, log);
            matches["GrandTotal"]    = Compare <decimal>(actual.FileName, "GrandTotal", actual.GrandTotal, (decimal)expected.GrandTotalValue, log);
            matches["ShippingTotal"] = Compare <decimal>(actual.FileName, "ShippingTotal", actual.ShippingTotal, (decimal)expected.ShippingTotalValue, log);
            matches["NetTotal"]      = Compare <decimal>(actual.FileName, "NetTotal", actual.NetTotal, (decimal)expected.PreTaxTotalValue, log);
            matches["VatAmount"]     = Compare <decimal>(actual.FileName, "VatAmount", actual.VatAmount, (decimal)expected.TaxTotalValue, log);
            matches["PostCode"]      = Compare <string>(actual.FileName, "PostCode", actual.PostCode, expected.PostalCode, log);
            matches["TaxDate"]       = Compare <DateTime>(actual.FileName, "TaxDate", actual.TaxDate ?? DateTime.MinValue, expected.DocumentDate, log);

            // A fully matched header is worth 20 points - so apportion that
            int     DOCUMENT_HEADER_POINTS = 20;
            decimal numPossibleMatches     = matches.Count();
            decimal numMatches             = matches.Where(m => m.Value == true).Count();
            var     successRate            = numMatches / numPossibleMatches;
            var     points = (int)(DOCUMENT_HEADER_POINTS * successRate);

            documentPoints = points;
            string notes = $"Document {actual.FileName} Header matched {numMatches} of {numPossibleMatches} fields for a score of {points} / {DOCUMENT_HEADER_POINTS})";

            log.LogInformation(notes);

            log.LogTrace($"Checking accuracy of document {actual.DocumentNumber} lines");
            matches = new Dictionary <string, bool> {
                { "ItemDescription", false }, { "UnitPrice", false }, { "Taxableindicator", false }, { "LineQuantity", false }, { "NetAmount", false }, { "DiscountPercent", false }
            };

            // 100 points for a fully matched document leaves 80 up for grabs: pro rata that over the expected lines
            int     DOCUMENT_LINES_POINTS = 80;
            decimal DOCUMENT_LINE_POINTS  = DOCUMENT_LINES_POINTS / expected.Lines.Count();

            numPossibleMatches = matches.Count();

            foreach (var expLine in expected.Lines.OrderBy(o => o.LineNumber))
            {
                var expLineNumber = expLine.LineNumber.PadLeft(2, '0');
                log.LogTrace($"Checking Line {expLineNumber}");
                DocumentLineItem actLine = null;
                try
                {
                    actLine = actual.LineItems.Where(l => l.DocumentLineNumber == expLineNumber).Single();
                }
                catch (Exception)
                {
                    log.LogTrace($"{actual.FileName} Actual line matching {expLineNumber} does not exist - you may want to retrain your model?");
                }
                if (actLine != null)
                {
                    matches["ItemDescription"] = Compare <string>(actual.FileName, "ItemDescription" + expLineNumber, actLine.ItemDescription, $"{expLine.ProductCode}{expLine.ProductDescription}".Trim(), log);
                    matches["UnitPrice"]       = Compare <decimal>(actual.FileName, "UnitPrice" + expLineNumber, actLine.UnitPrice, (decimal)expLine.Price, log);

                    bool actTaxIndicator = false;
                    if (!String.IsNullOrEmpty(actLine.Taxableindicator))
                    {
                        actTaxIndicator = true;
                    }
                    matches["Taxableindicator"] = Compare <bool>(actual.FileName, "Taxableindicator" + expLineNumber, actTaxIndicator, expLine.Taxable, log);

                    double actLineQuantity = 0;
                    if (Double.TryParse(actLine.LineQuantity, out double res))
                    {
                        actLineQuantity = res;
                    }
                    matches["LineQuantity"] = Compare <double>(actual.FileName, "LineQuantity" + expLineNumber, actLineQuantity, expLine.Quantity, log);
                    if (!matches["LineQuantity"])
                    {
                        matches["LineQuantity"] = Compare <double>(actual.FileName, "CalculatedLineQuantity" + expLineNumber, (double)actLine.CalculatedLineQuantity, expLine.Quantity, log);
                    }
                    matches["NetAmount"]       = Compare <decimal>(actual.FileName, "NetAmount" + expLineNumber, actLine.NetAmount, (decimal)expLine.DiscountedGoodsValue, log);
                    matches["DiscountPercent"] = Compare <decimal>(actual.FileName, "DiscountPercent" + expLineNumber, actLine.DiscountPercent, (decimal)expLine.Discount, log);

                    numMatches      = matches.Where(m => m.Value == true).Count();
                    successRate     = (numMatches / numPossibleMatches);
                    points          = (int)(DOCUMENT_LINE_POINTS * successRate);
                    documentPoints += points;
                    notes           = $"Document {actual.FileName} line {expLineNumber} matched {numMatches} of {numPossibleMatches} fields for a score of {points} / {DOCUMENT_LINE_POINTS})";
                    log.LogInformation(notes);
                }
                else
                {
                    log.LogTrace($"{actual.FileName} Actual line matching {expLineNumber} does not exist - you may want to retrain your model?");
                }
            }

            notes = $"Document {actual.FileName} overall scored {documentPoints} / 100 points)";
            if (documentPoints < 50)
            {
                log.LogError(notes);
            }
            else
            {
                log.LogInformation(notes);
            }
            results.Add(new ScoreRecord {
                Type = $"Accuracy", Notes = notes, Score = documentPoints
            });
            return(results);
        }
Example #2
0
        private async Task <List <ScoreRecord> > CheckIndividualDocuments(ILogger log)
        {
            var results = new List <ScoreRecord>();

            log.LogTrace($"Checking accuracy of document recognition");
            log.LogTrace($"Reading expected results from SQL Database");
            var checks = new List <DocumentCheckRequest>();

            using (SqlConnection connection = new SqlConnection(scoresSQLConnectionString))
            {
                connection.Open();
                SqlCommand command = connection.CreateCommand();
                command.Connection = connection;
                try
                {
                    string previousDocumentFormat = "";
                    string previousDocumentNumber = "";

                    command.CommandText = "SELECT * FROM [dbo].[GeneratedDocuments] order by DocumentFormat, DocumentNumber, LineNumber";
                    SqlDataReader reader = command.ExecuteReader();
                    try
                    {
                        DocumentCheckRequest checkRequest = null;
                        while (reader.Read())
                        {
                            bool   newDocument           = false;
                            string currentDocumentFormat = (string)reader["DocumentFormat"];
                            string currentDocumentNumber = (string)reader["DocumentNumber"];

                            if (currentDocumentFormat != previousDocumentFormat)
                            {
                                newDocument            = true;
                                previousDocumentFormat = currentDocumentFormat;
                            }
                            if (currentDocumentNumber != previousDocumentNumber)
                            {
                                newDocument            = true;
                                previousDocumentNumber = currentDocumentNumber;
                            }

                            if (newDocument)
                            {
                                if (checkRequest != null)
                                {
                                    checks.Add(checkRequest);
                                }
                                checkRequest = new DocumentCheckRequest
                                {
                                    Account            = (string)reader["Account"],
                                    DocumentNumber     = (string)reader["DocumentNumber"],
                                    DocumentDate       = (DateTime)reader["DocumentDate"],
                                    PostalCode         = (string)reader["PostalCode"],
                                    GrandTotalValue    = Convert.ToDouble(reader["GrandTotalValue"]),
                                    PreTaxTotalValue   = Convert.ToDouble(reader["PreTaxTotalValue"]),
                                    ShippingTotalValue = Convert.ToDouble(reader["ShippingTotalValue"]),
                                    TaxTotalValue      = Convert.ToDouble(reader["TaxTotalValue"]),
                                    FileName           = (string)reader["FileName"],
                                    DocumentFormat     = (string)reader["DocumentFormat"],
                                };
                            }

                            var line = new DocumentLineCheckRequest
                            {
                                Discount           = Convert.ToDouble(reader["Discount"]),
                                LineNumber         = (string)reader["LineNumber"],
                                ProductCode        = (string)reader["Isbn"],
                                ProductDescription = (string)reader["Title"],
                                Price                = Convert.ToDouble(reader["Price"]),
                                Quantity             = Convert.ToDouble(reader["Quantity"]),
                                Taxable              = (bool)reader["Taxable"],
                                DiscountedGoodsValue = Convert.ToDouble(reader["DiscountedGoodsValue"]),
                                DiscountValue        = Convert.ToDouble(reader["DiscountValue"]),
                                GoodsValue           = Convert.ToDouble(reader["GoodsValue"]),
                                TaxableValue         = Convert.ToDouble(reader["TaxableValue"]),
                            };
                            checkRequest.Lines.Add(line);
                        }
                        if (checkRequest != null)
                        {
                            checks.Add(checkRequest);
                        }
                    }
                    catch (Exception e)
                    {
                        log.LogError(e.Message);
                    }
                    finally
                    {
                        reader.Close();
                    }
                }
                catch (Exception e)
                {
                    log.LogError($"Exception prevented reading expected results from SQL database {connection.Database}  Message is {e.Message}");
                    throw e;
                }
                log.LogInformation($"Expected Results read from SQL database {connection.Database}");
            }

            log.LogInformation($"We will be checking the actual vs expected results for {checks.Count} documents");
            foreach (var check in checks)
            {
                string fileName = $"{check.DocumentFormat}-{check.FileName}";
                log.LogDebug($"Loading document {fileName} from processing database");
                Document document = null;
                try
                {
                    log.LogTrace($"Checking {fileName}");
                    document = HorusSql.LoadDocument(fileName, log);
                }
                catch (Exception)
                {
                    log.LogWarning($"Unable to load document {fileName} from processing database.");
                    continue;
                }
                if (document == null)
                {
                    log.LogTrace($"Document {check.DocumentNumber} has not been processed sucessfully and will be skipped");
                    continue;
                }
                var checkResults = CompareActualWithExpectedResults(document, check, log);
                results.AddRange(checkResults);
            }
            return(results);
        }