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