using Application.Abstractions.Data; using Domain.Entities.Stocks; using Microsoft.EntityFrameworkCore; using SharedKernel; namespace Infrastructure.StockData; /// /// KOSIS 파싱 결과 → MacroIndicator upsert (UQ = Code+Period). HTTP 와 분리된 순수 DB 반영 로직 (Seibro *Import 방식 미러링) — /// 통합 테스트가 파서 fixture 입력으로 직접 호출한다. 호출부(KosisSyncService)는 지표 하나의 응답 rows 를 넘긴다. /// 계열 특정: config ClassCode(C1) 가 지정되면 그 C1 행만, 비면 응답 첫 계열(첫 등장 C1)로 자동 고정해 여러 분류 혼입을 배제한다. /// public static class KosisImport { /// /// 지표 하나의 파싱 rows 를 (Code, Period) upsert. 반환 = (inserted, updated). SaveChanges 는 호출부가 수행하지 않으므로 여기서 저장한다. /// public static async Task<(int Inserted, int Updated)> UpsertAsync( IAppDbContext db, AppSettings.KosisSection.KosisIndicator indicator, IReadOnlyList rows, CancellationToken ct ) { var selected = SelectSeries(indicator.ClassCode, rows); if (selected.Count == 0) { return (0, 0); } var code = indicator.Code.Trim(); var periods = selected.Select(c => c.Period).ToHashSet(); var existing = await db.MacroIndicator.Where(c => c.Code == code && periods.Contains(c.Period)).ToListAsync(ct); var existingByPeriod = existing.ToDictionary(c => c.Period); var inserted = 0; var updated = 0; foreach (var row in selected) { if (existingByPeriod.TryGetValue(row.Period, out var entity)) { var before = (entity.Value, entity.Unit, entity.Name); entity.Update(indicator.Name, row.Value, row.Unit); if (before != (entity.Value, entity.Unit, entity.Name)) { updated++; } } else { var created = MacroIndicator.Create(code, indicator.Name, row.Period, row.Value, row.Unit); await db.MacroIndicator.AddAsync(created, ct); existingByPeriod[row.Period] = created; inserted++; } } if (inserted > 0 || updated > 0) { await db.SaveChangesAsync(ct); } return (inserted, updated); } /// /// 여러 분류가 섞인 rows 에서 원하는 한 계열만 고른다. classCode 지정 시 그 C1 행만, /// 비면 첫 등장 C1 값으로 고정 (KOSIS 통계표가 단일 계열이면 그대로 전부 채택). 시점(Period)당 1행이 되도록 첫 매치만 취한다. /// private static List SelectSeries(string classCode, IReadOnlyList rows) { var result = new List(); if (rows.Count == 0) { return result; } var target = classCode.Trim(); if (target.Length == 0) { target = rows[0].ClassCode; } var seenPeriods = new HashSet(); foreach (var row in rows) { if (row.ClassCode != target) { continue; } if (seenPeriods.Add(row.Period)) { result.Add(row); } } return result; } }