Files
QuantEngineByItz/src/dotnet/QuantEngine.Web/Endpoints/CollectionEndpoints.cs
T

343 lines
10 KiB
C#

using FastEndpoints;
using Hangfire;
using QuantEngine.Application.Interfaces;
using QuantEngine.Core.Interfaces;
namespace QuantEngine.Web.Endpoints;
public class GetCollectionStateEndpoint : EndpointWithoutRequest<CollectionDashboardStateRecord>
{
private readonly ICollectionReadModelService _readModelService;
public GetCollectionStateEndpoint(ICollectionReadModelService readModelService)
{
_readModelService = readModelService;
}
public override void Configure()
{
Get("/api/collection/state");
AllowAnonymous();
Description(d => d
.Produces<CollectionDashboardStateRecord>(200)
.Produces(500));
}
public override async Task HandleAsync(CancellationToken ct)
{
try
{
var state = await _readModelService.GetDashboardStateAsync();
await SendOkAsync(state, ct);
}
catch
{
await SendErrorsAsync(500, ct);
}
}
}
public class GetRecentRunsRequest
{
public int Limit { get; set; } = 20;
}
public class GetRecentRunsResponse
{
public List<CollectionRunRecord> Runs { get; set; } = new();
public int Count { get; set; }
}
public class GetRecentRunsEndpoint : Endpoint<GetRecentRunsRequest, GetRecentRunsResponse>
{
private readonly ICollectionReadModelService _readModelService;
public GetRecentRunsEndpoint(ICollectionReadModelService readModelService)
{
_readModelService = readModelService;
}
public override void Configure()
{
Get("/api/collection/runs");
AllowAnonymous();
Description(d => d
.Produces<GetRecentRunsResponse>(200)
.Produces(500));
}
public override async Task HandleAsync(GetRecentRunsRequest req, CancellationToken ct)
{
try
{
var runs = await _readModelService.GetRecentRunsAsync(req.Limit);
await SendOkAsync(new GetRecentRunsResponse { Runs = runs, Count = runs.Count }, ct);
}
catch
{
await SendErrorsAsync(500, ct);
}
}
}
public class GetRunSnapshotsRequest
{
public string RunId { get; set; } = "";
}
public class GetRunSnapshotsResponse
{
public string RunId { get; set; } = "";
public List<CollectionSnapshotRecord> Snapshots { get; set; } = new();
public int Count { get; set; }
}
public class GetRunSnapshotsEndpoint : Endpoint<GetRunSnapshotsRequest, GetRunSnapshotsResponse>
{
private readonly ICollectionReadModelService _readModelService;
public GetRunSnapshotsEndpoint(ICollectionReadModelService readModelService)
{
_readModelService = readModelService;
}
public override void Configure()
{
Get("/api/collection/runs/{RunId}/snapshots");
AllowAnonymous();
Description(d => d
.Produces<GetRunSnapshotsResponse>(200)
.Produces(404)
.Produces(500));
}
public override async Task HandleAsync(GetRunSnapshotsRequest req, CancellationToken ct)
{
try
{
var snapshots = await _readModelService.GetRunSnapshotsAsync(req.RunId);
await SendOkAsync(new GetRunSnapshotsResponse { RunId = req.RunId, Snapshots = snapshots, Count = snapshots.Count }, ct);
}
catch
{
await SendErrorsAsync(500, ct);
}
}
}
public class GetRunErrorsRequest
{
public string RunId { get; set; } = "";
public int Limit { get; set; } = 50;
}
public class GetRunErrorsResponse
{
public string RunId { get; set; } = "";
public List<CollectionErrorRecord> Errors { get; set; } = new();
public int Count { get; set; }
}
public class GetRunErrorsEndpoint : Endpoint<GetRunErrorsRequest, GetRunErrorsResponse>
{
private readonly ICollectionReadModelService _readModelService;
public GetRunErrorsEndpoint(ICollectionReadModelService readModelService)
{
_readModelService = readModelService;
}
public override void Configure()
{
Get("/api/collection/runs/{RunId}/errors");
AllowAnonymous();
Description(d => d
.Produces<GetRunErrorsResponse>(200)
.Produces(404)
.Produces(500));
}
public override async Task HandleAsync(GetRunErrorsRequest req, CancellationToken ct)
{
try
{
var errors = await _readModelService.GetRunErrorsAsync(req.RunId, req.Limit);
await SendOkAsync(new GetRunErrorsResponse { RunId = req.RunId, Errors = errors, Count = errors.Count }, ct);
}
catch
{
await SendErrorsAsync(500, ct);
}
}
}
public class GetLatestSnapshotsRequest
{
public string Ticker { get; set; } = "";
public int Limit { get; set; } = 10;
}
public class GetLatestSnapshotsResponse
{
public string Ticker { get; set; } = "";
public List<CollectionSnapshotRecord> Snapshots { get; set; } = new();
public int Count { get; set; }
}
public class GetLatestSnapshotsEndpoint : Endpoint<GetLatestSnapshotsRequest, GetLatestSnapshotsResponse>
{
private readonly ICollectionReadModelService _readModelService;
public GetLatestSnapshotsEndpoint(ICollectionReadModelService readModelService)
{
_readModelService = readModelService;
}
public override void Configure()
{
Get("/api/collection/latest/{Ticker}");
AllowAnonymous();
Description(d => d
.Produces<GetLatestSnapshotsResponse>(200)
.Produces(500));
}
public override async Task HandleAsync(GetLatestSnapshotsRequest req, CancellationToken ct)
{
try
{
var snapshots = await _readModelService.GetLatestSnapshotsForTickerAsync(req.Ticker, req.Limit);
await SendOkAsync(new GetLatestSnapshotsResponse { Ticker = req.Ticker, Snapshots = snapshots, Count = snapshots.Count }, ct);
}
catch
{
await SendErrorsAsync(500, ct);
}
}
}
public class GetPriceHistorySummaryResponse
{
public List<PriceHistorySummaryRecord> Tickers { get; set; } = new();
}
public class GetPriceHistorySummaryEndpoint : EndpointWithoutRequest<GetPriceHistorySummaryResponse>
{
private readonly ICollectionReadModelService _readModelService;
private readonly ILogger<GetPriceHistorySummaryEndpoint> _logger;
public GetPriceHistorySummaryEndpoint(ICollectionReadModelService readModelService, ILogger<GetPriceHistorySummaryEndpoint> logger)
{
_readModelService = readModelService;
_logger = logger;
}
public override void Configure()
{
Get("/api/collection/history-summary");
AllowAnonymous();
Description(d => d
.Produces<GetPriceHistorySummaryResponse>(200)
.Produces(500));
}
public override async Task HandleAsync(CancellationToken ct)
{
try
{
var summary = await _readModelService.GetPriceHistorySummaryAsync();
await SendOkAsync(new GetPriceHistorySummaryResponse { Tickers = summary }, ct);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to fetch price history summary");
await SendErrorsAsync(500, ct);
}
}
}
public class StartCollectionRunResponse
{
public string RunId { get; set; } = "";
}
public class StartCollectionRunEndpoint : EndpointWithoutRequest<StartCollectionRunResponse>
{
private readonly IBackgroundJobClient _jobClient;
private readonly IConfiguration _configuration;
private readonly ILogger<StartCollectionRunEndpoint> _logger;
public StartCollectionRunEndpoint(
IBackgroundJobClient jobClient,
IConfiguration configuration,
ILogger<StartCollectionRunEndpoint> logger)
{
_jobClient = jobClient;
_configuration = configuration;
_logger = logger;
}
public override void Configure()
{
Post("/api/collection/run");
Description(d => d
.Produces<StartCollectionRunResponse>(202)
.Produces(500));
}
public override async Task HandleAsync(CancellationToken ct)
{
try
{
var runId = $"api-{DateTime.Now:yyyyMMdd-HHmmss}";
var accountMode = _configuration["Kis:AccountMode"] ?? "mock";
var tickers = new[] { "005930", "000660", "051910", "005380", "010140", "005490" }.ToList();
_jobClient.Enqueue<ICollectionOrchestrator>(o => o.RunCollectionAsync(runId, accountMode, tickers));
_logger.LogInformation("Collection run {RunId} enqueued", runId);
await SendAsync(new StartCollectionRunResponse { RunId = runId }, 202, ct);
}
catch
{
await SendErrorsAsync(500, ct);
}
}
}
public class FactorVersionDto
{
public string VersionId { get; set; } = string.Empty;
public string CreatedAt { get; set; } = string.Empty;
public string Status { get; set; } = "ACTIVE";
public string Description { get; set; } = string.Empty;
}
public class GetFactorVersionsResponse
{
public List<FactorVersionDto> Versions { get; set; } = new();
}
public class GetFactorVersionsEndpoint : EndpointWithoutRequest<GetFactorVersionsResponse>
{
public override void Configure()
{
Get("/api/factors/versions");
AllowAnonymous();
Description(d => d.Produces<GetFactorVersionsResponse>(200));
}
public override async Task HandleAsync(CancellationToken ct)
{
var versions = new List<FactorVersionDto>
{
new() { VersionId = "FACTOR-V4.2", CreatedAt = DateTime.UtcNow.AddDays(-2).ToString("yyyy-MM-dd"), Status = "ACTIVE", Description = "최신 팩터 산출 공식 V4.2" },
new() { VersionId = "FACTOR-V4.1", CreatedAt = DateTime.UtcNow.AddDays(-12).ToString("yyyy-MM-dd"), Status = "ARCHIVED", Description = "이전 보정 공식 V4.1" },
new() { VersionId = "FACTOR-V4.0", CreatedAt = DateTime.UtcNow.AddDays(-30).ToString("yyyy-MM-dd"), Status = "ARCHIVED", Description = "기초 팩터 공식 V4.0" }
};
await SendOkAsync(new GetFactorVersionsResponse { Versions = versions }, ct);
}
}