Procházet zdrojové kódy

feat(market): 국내 요약에 S&P500·나스닥 카드 + 지도 국가당 1핀 dedupe

- GetDomesticSummary: 국내 3지수 뒤에 글로벌 2(S&P500 ^gspc, 나스닥 ^ixic) — WorldIndexSnapshot 심볼 조회 + 표시명 오버라이드
- GetWorldIndices: 한 국가에 지수 여러 개면(US ^gspc/^ixic) 심볼 오름차순 첫 행만 → 지도 US 핀은 S&P500 1개 유지
- Release 빌드 0에러

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
KIM-JINO5 před 2 týdny
rodič
revize
f10707923b

+ 28 - 1
Application/Features/Api/Stocks/GetDomesticSummary/Handler.cs

@@ -71,9 +71,13 @@ internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
         await AddIndexAsync(rows, "kospi", MarketIndexSeries.KOSPI, "코스피", indexDate, breadthByMarket, StockMarket.KOSPI, ct);
         // 코스닥
         await AddIndexAsync(rows, "kosdaq", MarketIndexSeries.KOSDAQ, "코스닥", indexDate, breadthByMarket, StockMarket.KOSDAQ, ct);
-        // KOSPI200 (KOSPI 계열 내 "200" 포함 지수) — 등락종목수 없음, 베이시스는 Phase 2
+        // KOSPI200 (KOSPI 계열 내 "200" 포함 지수) — 등락종목수 없음
         await AddKospi200Async(rows, indexDate, ct);
 
+        // 글로벌 벤치마크 — WorldIndexSnapshot 심볼로 조회 + 표시명 오버라이드 (등락종목수 없음)
+        await AddGlobalAsync(rows, "sp500", "^gspc", "S&P 500", ct);
+        await AddGlobalAsync(rows, "nasdaq", "^ixic", "나스닥", ct);
+
         return new Response { List = rows };
     }
 
@@ -139,6 +143,29 @@ internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
         });
     }
 
+    private async Task AddGlobalAsync(List<Response.Row> rows, string key, string symbol, string name, CancellationToken ct)
+    {
+        var idx = await db.WorldIndexSnapshot.AsNoTracking()
+            .Where(c => c.Symbol == symbol)
+            .Select(c => new { c.Close, c.ChangeVal, c.FlucRateBp, c.TradeDate })
+            .FirstOrDefaultAsync(ct);
+
+        if (idx is null)
+        {
+            return;
+        }
+
+        rows.Add(new Response.Row
+        {
+            Key = key,
+            Name = name,
+            Close = idx.Close,
+            ChangeVal = idx.ChangeVal,
+            FlucRateBp = idx.FlucRateBp,
+            TradeDate = idx.TradeDate.ToString("yyyy-MM-dd")
+        });
+    }
+
     private sealed class BreadthRow
     {
         public StockMarket Market { get; init; }

+ 28 - 8
Application/Features/Api/Stocks/GetWorldIndices/Handler.cs

@@ -46,11 +46,33 @@ internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
             }
         }
 
-        // 해외 — WorldIndexSnapshot (config Items 로 나라당 1개). KR 은 위에서 병합했으므로 제외.
-        var foreign = await db.WorldIndexSnapshot.AsNoTracking()
+        // 해외 — WorldIndexSnapshot. KR 은 위에서 병합했으므로 제외. 한 국가에 지수가 여러 개면(예: US ^gspc/^ixic)
+        // 지도는 나라당 1핀이므로 심볼 오름차순 첫 행만 남긴다(US → ^gspc=S&P500). 요약 카드는 별도 엔드포인트에서 심볼로 조회.
+        var foreignRaw = await db.WorldIndexSnapshot.AsNoTracking()
             .Where(c => c.CountryCode != "KR")
-            .OrderBy(c => c.CountryCode)
-            .Select(c => new Response.Row
+            .OrderBy(c => c.CountryCode).ThenBy(c => c.Symbol)
+            .Select(c => new
+            {
+                c.CountryCode,
+                c.Name,
+                c.ExchangeName,
+                c.Close,
+                c.ChangeVal,
+                c.FlucRateBp,
+                c.TradeDate
+            })
+            .ToListAsync(ct);
+
+        var seenCountry = new HashSet<string>();
+
+        foreach (var c in foreignRaw)
+        {
+            if (!seenCountry.Add(c.CountryCode))
+            {
+                continue;
+            }
+
+            rows.Add(new Response.Row
             {
                 CountryCode = c.CountryCode,
                 Name = c.Name,
@@ -59,10 +81,8 @@ internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
                 ChangeVal = c.ChangeVal,
                 FlucRateBp = c.FlucRateBp,
                 TradeDate = c.TradeDate
-            })
-            .ToListAsync(ct);
-
-        rows.AddRange(foreign);
+            });
+        }
 
         return new Response
         {