From e7e7d1470d5dc4a958da105e9b89e4d291e0e404 Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Mon, 29 Jun 2026 23:19:35 +0900 Subject: [PATCH] =?UTF-8?q?build(infra):=20KIS=20API=20=ED=81=B4=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EC=96=B8=ED=8A=B8=20=EC=8B=9C=EA=B7=B8=EB=8B=88?= =?UTF-8?q?=EC=B2=98=20=EC=9D=BC=EC=9B=90=ED=99=94=20=EB=B0=8F=20=EB=B9=8C?= =?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 수정: KisApiClient 메서드 반환 타입 Task → Task>로 통일 - 수정: GetOrRefreshTokenAsync ReadAsAsync → ReadFromJsonAsync로 변경 - 수정: PlaceholderKisApiClient IKisApiClient 인터페이스 완전 구현 - 수정: CollectionEndpoints 모든 WithOpenApi() 호출 제거 - 수정: Program.cs using 지시문 순서 재정렬 - 추가: Client/_Imports.razor Fluent UI 컴포넌트 네임스페이스 정의 이제 빌드 성공: QuantEngine.Web 프로젝트 컴파일 완료 (경고 0, 에러 0) Co-Authored-By: Claude Haiku 4.5 --- .../External/KisApiClient.cs | 35 ++++++------ .../Services/KisApiClient.cs | 2 +- .../QuantEngine.Web/Client/_Imports.razor | 15 ++++++ .../Endpoints/CollectionEndpoints.cs | 21 +++----- .../PlaceholderImplementations.cs | 53 ++++++++++++++++--- src/dotnet/QuantEngine.Web/Program.cs | 2 +- 6 files changed, 90 insertions(+), 38 deletions(-) create mode 100644 src/dotnet/QuantEngine.Web/Client/_Imports.razor diff --git a/src/dotnet/QuantEngine.Infrastructure/External/KisApiClient.cs b/src/dotnet/QuantEngine.Infrastructure/External/KisApiClient.cs index 9e2dc80..49d22be 100644 --- a/src/dotnet/QuantEngine.Infrastructure/External/KisApiClient.cs +++ b/src/dotnet/QuantEngine.Infrastructure/External/KisApiClient.cs @@ -171,10 +171,10 @@ namespace QuantEngine.Infrastructure.External return await response.Content.ReadAsStringAsync(); } - public Task GetCurrentPriceAsync(string code) + public async Task> GetCurrentPriceAsync(string code, string account = "mock") { - return SendRequestAsync( - "/uapi/domestic-stock/v1/quotations/inquire-price", + var json = await SendRequestAsync( + "/uapi/domestic-stock/v1/quotations/inquire-price", "FHKST01010100", new Dictionary { @@ -182,12 +182,13 @@ namespace QuantEngine.Infrastructure.External { "FID_INPUT_ISCD", code } } ); + return JsonSerializer.Deserialize>(json) ?? new(); } - public Task GetAskingPrice10LevelAsync(string code) + public async Task> GetAskingPrice10LevelAsync(string code, string account = "mock") { - return SendRequestAsync( - "/uapi/domestic-stock/v1/quotations/inquire-asking-price-exp-ccn", + var json = await SendRequestAsync( + "/uapi/domestic-stock/v1/quotations/inquire-asking-price-exp-ccn", "FHKST01010200", new Dictionary { @@ -195,12 +196,13 @@ namespace QuantEngine.Infrastructure.External { "FID_INPUT_ISCD", code } } ); + return JsonSerializer.Deserialize>(json) ?? new(); } - public Task GetDailyShortSaleAsync(string code, string startDate, string endDate) + public async Task> GetDailyShortSaleAsync(string code, string startDate, string endDate, string account = "mock") { - return SendRequestAsync( - "/uapi/domestic-stock/v1/quotations/daily-short-sale", + var json = await SendRequestAsync( + "/uapi/domestic-stock/v1/quotations/daily-short-sale", "FHPST04830000", new Dictionary { @@ -210,12 +212,13 @@ namespace QuantEngine.Infrastructure.External { "FID_INPUT_DATE_2", endDate } } ); + return JsonSerializer.Deserialize>(json) ?? new(); } - public Task GetDailyItemChartPriceAsync(string code, string startDate, string endDate, string period = "D") + public async Task> GetDailyItemChartPriceAsync(string code, string startDate, string endDate, string period = "D", string account = "mock") { - return SendRequestAsync( - "/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice", + var json = await SendRequestAsync( + "/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice", "FHKST03010100", new Dictionary { @@ -227,12 +230,13 @@ namespace QuantEngine.Infrastructure.External { "FID_ORG_ADJ_PRC", "0" } } ); + return JsonSerializer.Deserialize>(json) ?? new(); } - public Task GetInvestorTrendAsync(string code) + public async Task> GetInvestorTrendAsync(string code, string account = "mock") { - return SendRequestAsync( - "/uapi/domestic-stock/v1/quotations/inquire-investor", + var json = await SendRequestAsync( + "/uapi/domestic-stock/v1/quotations/inquire-investor", "FHKST01010900", new Dictionary { @@ -240,6 +244,7 @@ namespace QuantEngine.Infrastructure.External { "FID_INPUT_ISCD", code } } ); + return JsonSerializer.Deserialize>(json) ?? new(); } } } diff --git a/src/dotnet/QuantEngine.Infrastructure/Services/KisApiClient.cs b/src/dotnet/QuantEngine.Infrastructure/Services/KisApiClient.cs index 955c3d2..2873c69 100644 --- a/src/dotnet/QuantEngine.Infrastructure/Services/KisApiClient.cs +++ b/src/dotnet/QuantEngine.Infrastructure/Services/KisApiClient.cs @@ -173,7 +173,7 @@ public class KisApiClient : IKisApiClient ); response.EnsureSuccessStatusCode(); - var tokenData = await response.Content.ReadAsAsync>(); + var tokenData = await response.Content.ReadFromJsonAsync>(); var accessToken = tokenData["access_token"]?.ToString() ?? throw new InvalidOperationException("No access_token in response"); var expiresInStr = tokenData.ContainsKey("expires_in") ? tokenData["expires_in"]?.ToString() : "86400"; var expiresInSec = int.TryParse(expiresInStr, out var seconds) ? seconds : 86400; diff --git a/src/dotnet/QuantEngine.Web/Client/_Imports.razor b/src/dotnet/QuantEngine.Web/Client/_Imports.razor new file mode 100644 index 0000000..83a1805 --- /dev/null +++ b/src/dotnet/QuantEngine.Web/Client/_Imports.razor @@ -0,0 +1,15 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using Microsoft.FluentUI.AspNetCore.Components +@using Microsoft.FluentUI.AspNetCore.Components.Icons +@using QuantEngine.Web +@using QuantEngine.Web.Components +@using QuantEngine.Web.Components.Layout +@using QuantEngine.Web.Services +@using QuantEngine.Web.Services diff --git a/src/dotnet/QuantEngine.Web/Endpoints/CollectionEndpoints.cs b/src/dotnet/QuantEngine.Web/Endpoints/CollectionEndpoints.cs index 443ca0a..04222fa 100644 --- a/src/dotnet/QuantEngine.Web/Endpoints/CollectionEndpoints.cs +++ b/src/dotnet/QuantEngine.Web/Endpoints/CollectionEndpoints.cs @@ -8,44 +8,37 @@ public static class CollectionEndpoints public static void MapCollectionEndpoints(this WebApplication app) { var group = app.MapGroup("/api/collection") - .WithName("Collection") - .WithOpenApi(); + .WithName("Collection"); group.MapGet("/state", GetCollectionState) .WithName("GetCollectionState") - .WithOpenApi() .Produces(200) .Produces(500); group.MapGet("/runs", GetRecentRuns) .WithName("GetRecentRuns") - .WithOpenApi() .Produces(200) .Produces(500); group.MapGet("/runs/{runId}/snapshots", GetRunSnapshots) .WithName("GetRunSnapshots") - .WithOpenApi() .Produces(200) .Produces(404) .Produces(500); group.MapGet("/runs/{runId}/errors", GetRunErrors) .WithName("GetRunErrors") - .WithOpenApi() .Produces(200) .Produces(404) .Produces(500); group.MapGet("/latest/{ticker}", GetLatestSnapshotsForTicker) .WithName("GetLatestSnapshotsForTicker") - .WithOpenApi() .Produces(200) .Produces(500); group.MapPost("/run", StartCollectionRun) .WithName("StartCollectionRun") - .WithOpenApi() .Produces(202) .Produces(500); } @@ -57,7 +50,7 @@ public static class CollectionEndpoints var state = await repo.GetDashboardStateAsync(); return Results.Ok(state); } - catch (Exception ex) + catch { return Results.StatusCode(500); } @@ -70,7 +63,7 @@ public static class CollectionEndpoints var runs = await repo.GetRecentRunsAsync(limit); return Results.Ok(new { runs, count = runs.Count }); } - catch (Exception ex) + catch { return Results.StatusCode(500); } @@ -83,7 +76,7 @@ public static class CollectionEndpoints var snapshots = await repo.GetRunSnapshotsAsync(runId); return Results.Ok(new { runId, snapshots, count = snapshots.Count }); } - catch (Exception ex) + catch { return Results.StatusCode(500); } @@ -96,7 +89,7 @@ public static class CollectionEndpoints var errors = await repo.GetRunErrorsAsync(runId, limit); return Results.Ok(new { runId, errors, count = errors.Count }); } - catch (Exception ex) + catch { return Results.StatusCode(500); } @@ -109,7 +102,7 @@ public static class CollectionEndpoints var snapshots = await repo.GetLatestSnapshotsForTickerAsync(ticker, limit); return Results.Ok(new { ticker, snapshots, count = snapshots.Count }); } - catch (Exception ex) + catch { return Results.StatusCode(500); } @@ -170,7 +163,7 @@ public static class CollectionEndpoints startedAt = now }); } - catch (Exception ex) + catch { return Results.StatusCode(500); } diff --git a/src/dotnet/QuantEngine.Web/Infrastructure/PlaceholderImplementations.cs b/src/dotnet/QuantEngine.Web/Infrastructure/PlaceholderImplementations.cs index 727642a..f5ec92f 100644 --- a/src/dotnet/QuantEngine.Web/Infrastructure/PlaceholderImplementations.cs +++ b/src/dotnet/QuantEngine.Web/Infrastructure/PlaceholderImplementations.cs @@ -107,19 +107,58 @@ public class PlaceholderTokenCache : ITokenCache public class PlaceholderKisApiClient : IKisApiClient { - // Placeholder: To be implemented with actual KIS API calls - public Task GetAccessTokenAsync(string account = "mock") + public Task> GetCurrentPriceAsync(string code, string account = "mock") { - return Task.FromResult("placeholder_token"); + return Task.FromResult(new Dictionary + { + { "code", code }, + { "price", 0 }, + { "change", 0 }, + { "changeRate", 0 } + }); } - public Task GetQuotationAsync(string ticker, string account = "mock") + public Task> GetAskingPrice10LevelAsync(string code, string account = "mock") { - return Task.FromResult(null); + return Task.FromResult(new Dictionary + { + { "code", code }, + { "askLevels", new List() }, + { "bidLevels", new List() } + }); } - public Task GetRankingAsync(string sort = "price", int limit = 10, string account = "mock") + public Task> GetDailyShortSaleAsync(string code, string startDate, string endDate, string account = "mock") { - return Task.FromResult(null); + return Task.FromResult(new Dictionary + { + { "code", code }, + { "startDate", startDate }, + { "endDate", endDate }, + { "data", new List() } + }); + } + + public Task> GetDailyItemChartPriceAsync(string code, string startDate, string endDate, string period = "D", string account = "mock") + { + return Task.FromResult(new Dictionary + { + { "code", code }, + { "startDate", startDate }, + { "endDate", endDate }, + { "period", period }, + { "candles", new List() } + }); + } + + public Task> GetInvestorTrendAsync(string code, string account = "mock") + { + return Task.FromResult(new Dictionary + { + { "code", code }, + { "individual", 0 }, + { "foreign", 0 }, + { "institution", 0 } + }); } } diff --git a/src/dotnet/QuantEngine.Web/Program.cs b/src/dotnet/QuantEngine.Web/Program.cs index 80d7303..e1ff35b 100644 --- a/src/dotnet/QuantEngine.Web/Program.cs +++ b/src/dotnet/QuantEngine.Web/Program.cs @@ -8,6 +8,7 @@ using System.Text.Json; using Microsoft.FluentUI.AspNetCore.Components; using Serilog; using QuantEngine.Web.Infrastructure; +using QuantEngine.Web.Endpoints; // Serilog Configuration with Telegram Sink Log.Logger = new LoggerConfiguration() @@ -63,7 +64,6 @@ app.MapRazorComponents() .AddInteractiveServerRenderMode(); // Collection API Endpoints -using QuantEngine.Web.Endpoints; app.MapCollectionEndpoints(); app.MapGet("/api/history/{domain}", async (string domain, int? limit, IPostgresqlHistorySnapshotReader reader) =>