feat(ai): queue strategy snapshots
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
using JobTrackerApi.Data;
|
||||
using JobTrackerApi.Models;
|
||||
using JobTrackerApi.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace JobTrackerApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/jobapplications/{jobId:int}/focus-plan")]
|
||||
[Authorize(AuthenticationSchemes = "local", Policy = ProEntitlement.Policy)]
|
||||
public sealed class StrategySnapshotController(
|
||||
JobTrackerContext db,
|
||||
AiOperationAdmission admission,
|
||||
StrategySnapshotService snapshots) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<FocusPlanDto>> Get(int jobId, [FromQuery] string? attachmentIds, CancellationToken cancellationToken)
|
||||
{
|
||||
IReadOnlyList<int> ids;
|
||||
try { ids = StrategySnapshotService.ParseAttachmentIds(attachmentIds); }
|
||||
catch (StrategySnapshotValidationException exception) { return Problem(exception); }
|
||||
var result = await snapshots.GetCachedAsync(jobId, StrategySnapshotService.NormalizeAttachmentIds(ids), cancellationToken);
|
||||
return result is null
|
||||
? NotFound(new { code = "strategy_not_generated", message = "No strategy snapshot has been generated for this context." })
|
||||
: Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("operations")]
|
||||
public async Task<IActionResult> Enqueue(int jobId, [FromBody] StrategySnapshotRequest? request, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ids = StrategySnapshotService.ParseAttachmentIds(request?.AttachmentIds);
|
||||
await snapshots.ValidateRequestAsync(jobId, ids, cancellationToken);
|
||||
var signature = StrategySnapshotService.NormalizeAttachmentIds(ids);
|
||||
var subject = StrategySnapshotService.EncodeSubject(jobId, ids);
|
||||
var activeStatuses = new[] { OperationStatuses.Queued, OperationStatuses.Running, OperationStatuses.WaitingForRetry, OperationStatuses.WaitingForExternalFallback };
|
||||
var active = await db.UserOperations.AsNoTracking()
|
||||
.Where(item => item.TaskType == StrategySnapshotService.TaskType && item.SubjectId == subject && activeStatuses.Contains(item.Status))
|
||||
.OrderByDescending(item => item.CreatedAtUtc)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
if (active is not null)
|
||||
{
|
||||
var statusUrl = $"/api/operations/{active.Id:D}";
|
||||
return Accepted(statusUrl, new StrategySnapshotOperationResponse(OperationDto.From(active), statusUrl, false));
|
||||
}
|
||||
var key = await snapshots.BuildIdempotencyKeyAsync(jobId, signature, cancellationToken);
|
||||
var result = await admission.EnqueueAsync(
|
||||
StrategySnapshotService.TaskType,
|
||||
key,
|
||||
"job_strategy",
|
||||
subject,
|
||||
AiOperationPriorities.Interactive,
|
||||
cancellationToken);
|
||||
return Accepted(result.StatusUrl, new StrategySnapshotOperationResponse(OperationDto.From(result.Operation), result.StatusUrl, result.Created));
|
||||
}
|
||||
catch (StrategySnapshotValidationException exception) { return Problem(exception); }
|
||||
catch (AiOperationAdmissionException exception)
|
||||
{
|
||||
if (exception.RetryAfterSeconds is int seconds) Response.Headers.RetryAfter = seconds.ToString();
|
||||
return StatusCode(exception.StatusCode, new { code = exception.Code, message = exception.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("operation")]
|
||||
public async Task<ActionResult<OperationDto>> LatestOperation(int jobId, [FromQuery] string? attachmentIds, CancellationToken cancellationToken)
|
||||
{
|
||||
IReadOnlyList<int> ids;
|
||||
try { ids = StrategySnapshotService.ParseAttachmentIds(attachmentIds); }
|
||||
catch (StrategySnapshotValidationException exception) { return Problem(exception); }
|
||||
var subject = StrategySnapshotService.EncodeSubject(jobId, ids);
|
||||
var operation = await db.UserOperations.AsNoTracking()
|
||||
.Where(item => item.TaskType == StrategySnapshotService.TaskType && item.SubjectId == subject)
|
||||
.OrderByDescending(item => item.CreatedAtUtc)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
return operation is null ? NotFound() : Ok(OperationDto.From(operation));
|
||||
}
|
||||
|
||||
private ObjectResult Problem(StrategySnapshotValidationException exception) =>
|
||||
StatusCode(exception.StatusCode, new { code = exception.Code, message = exception.Message });
|
||||
}
|
||||
|
||||
public sealed record StrategySnapshotRequest(string? AttachmentIds);
|
||||
public sealed record StrategySnapshotOperationResponse(OperationDto Operation, string StatusUrl, bool Created);
|
||||
Reference in New Issue
Block a user