diff --git a/src/Mindee.Cli/Commands/PredictInvoiceCommand.cs b/src/Mindee.Cli/Commands/PredictInvoiceCommand.cs index 517bb453..88017fce 100644 --- a/src/Mindee.Cli/Commands/PredictInvoiceCommand.cs +++ b/src/Mindee.Cli/Commands/PredictInvoiceCommand.cs @@ -40,9 +40,9 @@ public async Task InvokeAsync(InvocationContext context) var invoicePrediction = await _mindeeClient .LoadDocument(new FileInfo(Path)) - .ParseAsync(WithWords); + .ParseAsync(WithWords); - if(Output == "summary") + if (Output == "summary") { context.Console.Out.Write(invoicePrediction != null ? invoicePrediction.Inference.Prediction.ToString()! : "null"); } diff --git a/src/Mindee/Parsing/Common/FinancialPredictionBase.cs b/src/Mindee/Parsing/Common/FinancialPredictionBase.cs index a59209ab..36383719 100644 --- a/src/Mindee/Parsing/Common/FinancialPredictionBase.cs +++ b/src/Mindee/Parsing/Common/FinancialPredictionBase.cs @@ -20,12 +20,6 @@ public abstract class FinancialPredictionBase : PredictionBase [JsonPropertyName("date")] public Date Date { get; set; } - /// - /// The supplier name. - /// - [JsonPropertyName("supplier")] - public StringField Supplier { get; set; } - /// /// /// diff --git a/src/Mindee/Parsing/Invoice/InvoiceLineItem.cs b/src/Mindee/Parsing/Invoice/InvoiceLineItem.cs new file mode 100644 index 00000000..86a32252 --- /dev/null +++ b/src/Mindee/Parsing/Invoice/InvoiceLineItem.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Text; +using System.Text.Json.Serialization; +using Mindee.Parsing.Common; + +namespace Mindee.Parsing.Invoice +{ + /// + /// Line items details. + /// + public sealed class InvoiceLineItem : FinancialPredictionBase + { + /// + /// The product code referring to the item. + /// + [JsonPropertyName("product_code")] + public string ProductCode { get; set; } + + /// + /// The item description. + /// + [JsonPropertyName("description")] + public string Description { get; set; } + + /// + /// The item quantity. + /// + [JsonPropertyName("quantity")] + public double? Quantity { get; set; } + + /// + /// The item unit price. + /// + [JsonPropertyName("unit_price")] + public double? UnitPrice { get; set; } + + /// + /// The item total amount. + /// + [JsonPropertyName("total_amount")] + public double? TotalAmount { get; set; } + + /// + /// The item tax rate in percentage. + /// + [JsonPropertyName("tax_rate")] + public double? TaxRate { get; set; } + + /// + /// The item tax amount. + /// + [JsonPropertyName("tax_amount")] + public double? TaxAmount { get; set; } + + /// + /// Confidence score. + /// + [JsonPropertyName("confidence")] + public double Confidence { get; set; } = 0.0; + + /// + /// The document page on which the information was found. + /// + [JsonPropertyName("page_id")] + public double PageId { get; set; } + + /// + /// Contains the relative vertices coordinates (points) of a polygon containing + /// the field in the document. + /// + [JsonPropertyName("polygon")] + public List> Polygon { get; set; } + + /// + /// A prettier reprensentation of the current model values. + /// + public override string ToString() + { + string productCode = ProductCode?.ToString() ?? ""; + string quantity = Quantity?.ToString() ?? ""; + string unitPrice = UnitPrice?.ToString() ?? ""; + string totalAmount = TotalAmount?.ToString() ?? ""; + string tax = TotalAmount?.ToString() ?? ""; + tax += TaxRate != null ? $" ({TaxRate} %)" : ""; + string description = Description ?? ""; + if (description.Length > 32) + { + description = description.Substring(0, 32) + "..."; + } + + return string.Join(" | ", + productCode.PadRight(14), + quantity.PadRight(6), + unitPrice.PadRight(7), + totalAmount.PadRight(8), + tax.PadRight(14), + description + ); + } + } +} diff --git a/src/Mindee/Parsing/Invoice/InvoiceV3Prediction.cs b/src/Mindee/Parsing/Invoice/InvoiceV3Prediction.cs index 3acebb52..dfa4ad9b 100644 --- a/src/Mindee/Parsing/Invoice/InvoiceV3Prediction.cs +++ b/src/Mindee/Parsing/Invoice/InvoiceV3Prediction.cs @@ -24,6 +24,12 @@ public sealed class InvoiceV3Prediction : FinancialPredictionBase [JsonPropertyName("customer")] public StringField Customer { get; set; } + /// + /// The supplier name. + /// + [JsonPropertyName("supplier")] + public StringField Supplier { get; set; } + /// /// The adress of the customer. /// diff --git a/src/Mindee/Parsing/Invoice/InvoiceV4Prediction.cs b/src/Mindee/Parsing/Invoice/InvoiceV4Prediction.cs new file mode 100644 index 00000000..dcef4cab --- /dev/null +++ b/src/Mindee/Parsing/Invoice/InvoiceV4Prediction.cs @@ -0,0 +1,121 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using Mindee.Parsing.Common; + +namespace Mindee.Parsing.Invoice +{ + /// + /// The invoice model for the v4. + /// + [Endpoint("invoices", "4")] + public sealed class InvoiceV4Prediction : FinancialPredictionBase + { + /// + /// The supplier name. + /// + [JsonPropertyName("supplier_name")] + public StringField SupplierName { get; set; } + + /// + /// List of + /// + [JsonPropertyName("supplier_company_registrations")] + public List SupplierCompanyRegistrations { get; set; } + + /// + /// List of payment details. + /// + [JsonPropertyName("supplier_payment_details")] + public List SupplierPaymentDetails { get; set; } + + /// + /// The supplier address. + /// + [JsonPropertyName("supplier_address")] + public StringField SupplierAddress { get; set; } + + /// + /// The customer. + /// + [JsonPropertyName("customer_name")] + public StringField CustomerName { get; set; } + + /// + /// List of customer company registrations. + /// + [JsonPropertyName("customer_company_registrations")] + public List CustomerCompanyRegistrations { get; set; } + + /// + /// The adress of the customer. + /// + [JsonPropertyName("customer_address")] + public StringField CustomerAddress { get; set; } + + /// + /// The due date of the invoice. + /// + [JsonPropertyName("due_date")] + public Date DueDate { get; set; } + + /// + /// The invoice number. + /// + [JsonPropertyName("invoice_number")] + public StringField InvoiceNumber { get; set; } + + /// + /// Total amount including taxes. + /// + [JsonPropertyName("total_amount")] + public AmountField TotalAmount { get; set; } + + /// + /// Total amount excluding taxes. + /// + [JsonPropertyName("total_net")] + public AmountField TotalNet { get; set; } + + /// + /// Line items details. + /// + [JsonPropertyName("line_items")] + public List LineItems { get; set; } + + /// + /// A prettier reprensentation of the current model values. + /// + public override string ToString() + { + string lineItems = "\n"; + if (LineItems.Any()) + { + lineItems = + "\n Code | QTY | Price | Amount | Tax (Rate) | Description\n "; + lineItems += string.Join("\n ", LineItems.Select(item => item.ToString())); + } + + StringBuilder result = new StringBuilder("----- Invoice v4 -----\n"); + result.Append($"Invoice number: {InvoiceNumber.Value}\n"); + result.Append($"Locale: {Locale}\n"); + result.Append($"Invoice date: {Date.Value}\n"); + result.Append($"Invoice due date: {DueDate.Value}\n"); + result.Append($"Supplier name: {SupplierName.Value}\n"); + result.Append($"Supplier address: {SupplierAddress.Value}\n"); + result.Append($"Supplier company registrations: {string.Join("\n ", SupplierCompanyRegistrations.Select(c => c.Value))}\n"); + result.Append($"Supplier payment details: {string.Join("\n ", SupplierPaymentDetails.Select(p => p))}\n"); + result.Append($"Customer name: {CustomerName.Value}\n"); + result.Append($"Customer company registrations: {string.Join("; ", CustomerCompanyRegistrations.Select(c => c.Value))}\n"); + result.Append($"Customer address: {string.Join("; ", CustomerAddress.Value)}\n"); + result.Append($"Taxes: {string.Join("\n ", Taxes.Select(t => t))}\n"); + result.Append($"Line items: {lineItems}\n"); + result.Append($"Total amount including taxes: {TotalAmount.Value}\n"); + result.Append($"Total amount excluding taxes: {TotalNet.Value}\n"); + result.Append("----------------------"); + + return result.ToString(); + } + } +} diff --git a/src/Mindee/Parsing/Receipt/ReceiptV4Prediction.cs b/src/Mindee/Parsing/Receipt/ReceiptV4Prediction.cs index d5ac3de8..07423da6 100644 --- a/src/Mindee/Parsing/Receipt/ReceiptV4Prediction.cs +++ b/src/Mindee/Parsing/Receipt/ReceiptV4Prediction.cs @@ -17,6 +17,12 @@ public sealed class ReceiptV4Prediction : FinancialPredictionBase [JsonPropertyName("category")] public StringField Category { get; set; } + /// + /// The supplier name. + /// + [JsonPropertyName("supplier")] + public StringField Supplier { get; set; } + /// /// /// diff --git a/tests/Mindee.UnitTests/Parsing/Invoice/InvoiceV4Test.cs b/tests/Mindee.UnitTests/Parsing/Invoice/InvoiceV4Test.cs new file mode 100644 index 00000000..79563cad --- /dev/null +++ b/tests/Mindee.UnitTests/Parsing/Invoice/InvoiceV4Test.cs @@ -0,0 +1,212 @@ +using Mindee.Parsing; +using Mindee.Parsing.Invoice; + +namespace Mindee.UnitTests.Parsing.Invoice +{ + public class InvoiceV4Test + { + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccess() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.NotNull(invoicePrediction); + } + + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccessForInvoiceNumber() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.Equal("0042004801351", + invoicePrediction.Inference.Pages.First().Prediction.InvoiceNumber.Value); + } + + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccessForCustomerName() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.Equal(0, invoicePrediction.Inference.Pages.First().Prediction.CustomerName.Confidence); + Assert.Equal(0, invoicePrediction.Inference.Pages.First().Id); + Assert.Equal(new List>() + , invoicePrediction.Inference.Pages.First().Prediction.CustomerName.Polygon); + Assert.Null(invoicePrediction.Inference.Pages.First().Prediction.CustomerName.Value); + } + + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccessForCustomerCompanyRegistrations() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.Equal("FR00000000000", + invoicePrediction.Inference.Prediction.CustomerCompanyRegistrations.First().Value); + Assert.Equal("111222333", + invoicePrediction.Inference.Prediction.CustomerCompanyRegistrations.Last().Value); + } + + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccessForCustomerAddress() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.Equal("1954 Bloon Street West Toronto, ON, M6P 3K9 Canada", + invoicePrediction.Inference.Pages.Last().Prediction.CustomerAddress.Value); + } + + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccessForDate() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.Equal(0.99, invoicePrediction.Inference.Pages.First().Prediction.Date.Confidence); + Assert.Equal(0, invoicePrediction.Inference.Pages.First().Id); + Assert.Equal("2020-02-17", invoicePrediction.Inference.Pages.First().Prediction.Date.Value); + Assert.Equal(new List>() + { + new List() { 0.382, 0.306 }, + new List() { 0.44, 0.306 }, + new List() { 0.44, 0.318 }, + new List() { 0.382, 0.318 }, + } + , invoicePrediction.Inference.Pages.First().Prediction.Date.Polygon); + } + + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccessForDueDate() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.Equal("2020-02-17", invoicePrediction.Inference.Pages.First().Prediction.DueDate.Value); + } + + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccessForDocumentType() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.Equal("INVOICE", invoicePrediction.Inference.Pages.First().Prediction.DocumentType.Value); + } + + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccessForLocale() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.Equal("fr", invoicePrediction.Inference.Pages.First().Prediction.Locale.Language); + Assert.Equal("EUR", invoicePrediction.Inference.Pages.First().Prediction.Locale.Currency); + } + + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccessForTotalTaxesIncluded() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.Equal(587.95, invoicePrediction.Inference.Pages.First().Prediction.TotalAmount.Value); + } + + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccessForTotalTaxesExcluded() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.Equal(489.97, invoicePrediction.Inference.Pages.First().Prediction.TotalNet.Value); + } + + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccessForSupplierName() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.Equal("TURNPIKE DESIGNS CO.", invoicePrediction.Inference.Pages.Last().Prediction.SupplierName.Value); + } + + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccessForSupplierAddress() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.Equal("156 University Ave, Toronto ON, Canada M5H 2H7", + invoicePrediction.Inference.Pages.Last().Prediction.SupplierAddress.Value); + } + + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccessForSupplierCompanyRegistrations() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.Equal("501124705", invoicePrediction.Inference.Pages.First().Prediction.SupplierCompanyRegistrations.First().Value); + Assert.Equal("FR33501124705", invoicePrediction.Inference.Pages.First().Prediction.SupplierCompanyRegistrations.Last().Value); + } + + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccessForSupplierPaymentDetails() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.Equal("FR7640254025476501124705368", invoicePrediction.Inference.Pages.First().Prediction.SupplierPaymentDetails.First().Iban); + } + + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccessForLineItems() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.NotEmpty(invoicePrediction.Inference.Prediction.LineItems); + + Assert.Equal("XXX81125600010", invoicePrediction.Inference.Prediction.LineItems.Skip(2).First().ProductCode); + Assert.Equal(1.0, invoicePrediction.Inference.Prediction.LineItems.Skip(2).First().Quantity); + Assert.Equal("a long string describing the item", + invoicePrediction.Inference.Prediction.LineItems.Skip(2).First().Description); + Assert.Equal(4.31, invoicePrediction.Inference.Prediction.LineItems.First().TotalAmount); + Assert.Equal(2.1, invoicePrediction.Inference.Prediction.LineItems.First().TaxRate); + } + + [Fact] + [Trait("Category", "Invoice V4")] + public async Task Predict_MustSuccessForOrientation() + { + var mindeeAPi = GetMindeeApiForInvoice(); + var invoicePrediction = await mindeeAPi.PredictAsync(ParsingTestBase.GetFakePredictParameter()); + + Assert.Equal(0, invoicePrediction.Inference.Pages.First().Orientation.Value); + } + + private MindeeApi GetMindeeApiForInvoice(string fileName = "Resources/invoice/response_v4/complete.json") + { + return ParsingTestBase.GetMindeeApi(fileName); + } + } +} diff --git a/tests/resources b/tests/resources index 8a34f0db..5d386213 160000 --- a/tests/resources +++ b/tests/resources @@ -1 +1 @@ -Subproject commit 8a34f0db9004c6538f079d3d2f4f41e3bf0b44d6 +Subproject commit 5d3862137de4543300497069d12177380437730b