using FiscalFlow.Core.Interfaces; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; namespace FiscalFlow.Infrastructure.Services; public class OcrService : IOcrService { private readonly HttpClient _httpClient; private readonly ILogger _logger; private readonly string _apiUrl; private readonly string? _apiKey; public OcrService(IHttpClientFactory httpClientFactory, IConfiguration configuration, ILogger logger) { _httpClient = httpClientFactory.CreateClient(); _logger = logger; _apiUrl = configuration["Ocr:ApiUrl"] ?? "http://localhost:8000/api/v1"; _apiKey = configuration["Ocr:ApiKey"]; } public async Task ExtractAsync(Stream fileStream, string fileName, CancellationToken cancellationToken = default) { try { var content = new MultipartFormDataContent(); var streamContent = new StreamContent(fileStream); streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); content.Add(streamContent, "file", fileName); var request = new HttpRequestMessage(HttpMethod.Post, $"{_apiUrl}/infer") { Content = content }; if (!string.IsNullOrEmpty(_apiKey)) { request.Headers.Add("X-API-Key", _apiKey); } var response = await _httpClient.SendAsync(request, cancellationToken); var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); if (!response.IsSuccessStatusCode) { _logger.LogError("OCR API error: {StatusCode} - {Content}", response.StatusCode, responseContent); return new OcrResult { Success = false, ErrorMessage = $"OCR API returned {response.StatusCode}" }; } var result = JsonSerializer.Deserialize(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); if (result == null) { return new OcrResult { Success = false, ErrorMessage = "Invalid OCR response" }; } return new OcrResult { Success = result.Success, Data = MapToInvoiceData(result.Fields), Confidence = result.Confidence, ErrorMessage = result.Error }; } catch (Exception ex) { _logger.LogError(ex, "Error calling OCR API"); return new OcrResult { Success = false, ErrorMessage = ex.Message }; } } private static InvoiceData MapToInvoiceData(Dictionary? fields) { if (fields == null) { return new InvoiceData(); } return new InvoiceData { SupplierName = GetFieldValue(fields, "supplier_name"), SupplierOrgNumber = GetFieldValue(fields, "supplier_org_number"), InvoiceNumber = GetFieldValue(fields, "invoice_number"), InvoiceDate = ParseDate(GetFieldValue(fields, "invoice_date")), DueDate = ParseDate(GetFieldValue(fields, "due_date")), AmountTotal = ParseDecimal(GetFieldValue(fields, "amount_total")), AmountVat = ParseDecimal(GetFieldValue(fields, "amount_vat")), VatRate = ParseInt(GetFieldValue(fields, "vat_rate")), OcrNumber = GetFieldValue(fields, "ocr_number"), Bankgiro = GetFieldValue(fields, "bankgiro"), Plusgiro = GetFieldValue(fields, "plusgiro"), Currency = GetFieldValue(fields, "currency") ?? "SEK" }; } private static string? GetFieldValue(Dictionary fields, string key) { return fields.TryGetValue(key, out var field) ? field.Value : null; } private static DateTime? ParseDate(string? value) { if (string.IsNullOrEmpty(value)) return null; if (DateTime.TryParse(value, out var date)) return date; return null; } private static decimal? ParseDecimal(string? value) { if (string.IsNullOrEmpty(value)) return null; value = value.Replace(",", "").Replace(" ", ""); if (decimal.TryParse(value, out var result)) return result; return null; } private static int? ParseInt(string? value) { if (string.IsNullOrEmpty(value)) return null; if (int.TryParse(value, out var result)) return result; return null; } } public class OcrApiResponse { public bool Success { get; set; } public string? Error { get; set; } public Dictionary? Fields { get; set; } public decimal Confidence { get; set; } } public class OcrField { public string? Value { get; set; } public decimal Confidence { get; set; } }