feat(cv-import): ground reviewed facts in revisioned career profiles
This commit is contained in:
@@ -94,6 +94,8 @@ namespace JobTrackerApi.Services
|
||||
|
||||
public interface ISummarizerService : IAiService
|
||||
{
|
||||
Task<CvImportDocument?> ExtractCvDocumentAsync(Stream stream, string fileName, string? contentType = null, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<CvImportDocument?>(null);
|
||||
new Task<string?> SummarizeSectionAsync(string instruction, string text, int maxLength = 180, int minLength = 40);
|
||||
|
||||
// Older providers cannot approve factual changes without implementing this review boundary.
|
||||
@@ -492,6 +494,32 @@ namespace JobTrackerApi.Services
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CvImportDocument?> ExtractCvDocumentAsync(Stream stream, string fileName, string? contentType = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var form = new MultipartFormDataContent();
|
||||
using var file = new StreamContent(stream);
|
||||
file.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType ?? "application/octet-stream");
|
||||
form.Add(file, "file", Path.GetFileName(fileName));
|
||||
using var response = await _httpFactory.CreateClient("ai-service").PostAsync("/cv-import/extract", form, cancellationToken);
|
||||
if (!response.IsSuccessStatusCode) throw new AiGenerationException("cv_extraction_failed", "Document extraction is unavailable or the document could not be parsed.", (int)response.StatusCode >= 500);
|
||||
await using var body = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||
using var bounded = new MemoryStream();
|
||||
var buffer = new byte[8192];
|
||||
int read;
|
||||
while ((read = await body.ReadAsync(buffer, cancellationToken)) > 0)
|
||||
{
|
||||
if (bounded.Length + read > 2 * 1024 * 1024) throw new InvalidOperationException("Document extraction response exceeded its limit.");
|
||||
await bounded.WriteAsync(buffer.AsMemory(0, read), cancellationToken);
|
||||
}
|
||||
using var json = JsonDocument.Parse(bounded.ToArray());
|
||||
var root = json.RootElement;
|
||||
var text = root.GetProperty("text").GetString() ?? "";
|
||||
var blocks = root.GetProperty("blocks").EnumerateArray().Select(b => new CvImportBlock(b.GetProperty("id").GetString() ?? "", b.GetProperty("text").GetString() ?? "", b.GetProperty("kind").GetString() ?? "text", b.TryGetProperty("page", out var page) && page.ValueKind == JsonValueKind.Number ? page.GetInt32() : null)).ToList();
|
||||
var warnings = root.GetProperty("warnings").EnumerateArray().Select(w => w.GetString() ?? "").ToList();
|
||||
if (string.IsNullOrWhiteSpace(text) || text.Length > 500000 || blocks.Count is 0 or > 10000 || blocks.Any(b => b.Id.Length is 0 or > 128 || b.Text.Length > 100000 || b.Page is < 1 or > 1000) || blocks.Select(b => b.Id).Distinct().Count() != blocks.Count || warnings.Count > 100 || warnings.Any(w => w.Length > 1000)) throw new InvalidOperationException("Document extraction returned invalid evidence.");
|
||||
return new CvImportDocument(text, root.GetProperty("ocr_used").GetBoolean(), root.GetProperty("content_type").GetString(), root.TryGetProperty("page_count", out var pages) && pages.ValueKind == JsonValueKind.Number ? pages.GetInt32() : null, text.Length, Path.GetFileName(fileName), blocks, warnings, root.GetProperty("method").GetString() ?? "docling");
|
||||
}
|
||||
|
||||
public async Task<AiTextExtractionResult?> ExtractTextAsync(Stream stream, string fileName, string? contentType = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
|
||||
Reference in New Issue
Block a user