feat(web): implement market-close caching mechanism for collection data
Validators (Pushes and Pull Requests) / validate-ui-and-storage (push) Successful in 22s
Prepare Release / Build & Create Release (push) Successful in 1m1s
Validators (Pushes and Pull Requests) / validate-core (push) Has been cancelled
Prepare Release / Release Notification (push) Successful in 1s

This commit is contained in:
2026-07-12 13:17:37 +09:00
parent 6db27fd634
commit 4eb23a41ca
@@ -56,26 +56,53 @@ public class KisDataCollectionOrchestrator : ICollectionOrchestrator
try
{
_logger.LogInformation("Collecting ticker {Ticker} (run {RunId})", ticker, runId);
var kisResult = await kisSource.GetPriceDataAsync(ticker, account);
var seedRow = new Dictionary<string, object> { { "Ticker", ticker } };
var (normalized, provenance) = _normalizer.NormalizeCollectionRow(seedRow, kisResult, null, false);
CollectionSnapshotRecord? cachedSnapshot = null;
if (IsMarketClosed())
{
var latest = await _repository.GetLatestSnapshotsForTickerAsync(ticker, 1);
var todayPrefix = DateTime.UtcNow.AddHours(9).ToString("yyyy-MM-dd");
if (latest.Count > 0 && latest[0].CapturedAt.StartsWith(todayPrefix))
{
cachedSnapshot = latest[0];
}
}
Dictionary<string, object> normalized;
string sourceName;
if (cachedSnapshot != null)
{
_logger.LogInformation("Cache hit for ticker {Ticker} (run {RunId})", ticker, runId);
normalized = JsonSerializer.Deserialize<Dictionary<string, object>>(cachedSnapshot.PayloadJson)
?? new Dictionary<string, object>();
sourceName = cachedSnapshot.SourceName.EndsWith(" (Cached)")
? cachedSnapshot.SourceName
: cachedSnapshot.SourceName + " (Cached)";
}
else
{
var kisResult = await kisSource.GetPriceDataAsync(ticker, account);
var seedRow = new Dictionary<string, object> { { "Ticker", ticker } };
var (norm, provenance) = _normalizer.NormalizeCollectionRow(seedRow, kisResult, null, false);
normalized = norm;
sourceName = (string)(provenance.GetValueOrDefault("source") ?? "kis_open_api");
}
// Save to DB
await _repository.SaveSnapshotAsync(new CollectionSnapshotRecord(
RunId: runId,
DatasetName: "data_feed",
Ticker: ticker,
SourceName: (string)(provenance.GetValueOrDefault("source") ?? "kis_open_api"),
SourceName: sourceName,
PayloadJson: JsonSerializer.Serialize(normalized),
CapturedAt: DataNormalizationHelper.KstNowIso()
));
// Track source
var source = (string)(provenance.GetValueOrDefault("source") ?? "kis_open_api");
if (!sourceCounts.ContainsKey(source))
sourceCounts[source] = 0;
sourceCounts[source]++;
if (!sourceCounts.ContainsKey(sourceName))
sourceCounts[sourceName] = 0;
sourceCounts[sourceName]++;
rows.Add(normalized);
result.SuccessCount++;
@@ -176,6 +203,23 @@ public class KisDataCollectionOrchestrator : ICollectionOrchestrator
return Path.Combine(Path.GetTempPath(), "kis_dotnet_collection_v1.json");
}
private static bool IsMarketClosed()
{
// KST Time conversion (UTC+9)
var kst = DateTime.UtcNow.AddHours(9);
// Weekend check
if (kst.DayOfWeek == DayOfWeek.Saturday || kst.DayOfWeek == DayOfWeek.Sunday)
return true;
// Market hours check (09:00 - 15:30)
var time = kst.TimeOfDay;
if (time < new TimeSpan(9, 0, 0) || time > new TimeSpan(15, 30, 0))
return true;
return false;
}
}