using System.Text.Json; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace QuantEngine.Application.Services; /// /// Lightweight startup bootstrap for collection scheduling/readiness. /// Writes a deterministic artifact so deployment can verify the collection /// pipeline entry point without forcing a live collection run. /// public sealed class CollectionBootstrapHostedService : IHostedService { private readonly ILogger _logger; private readonly GatherTradingDataParser _parser; public CollectionBootstrapHostedService( ILogger logger, GatherTradingDataParser parser) { _logger = logger; _parser = parser; } public Task StartAsync(CancellationToken cancellationToken) { try { var repoRoot = FindRepoRoot(); var outputPath = Path.Combine(repoRoot, "Temp", "collection_bootstrap_v1.json"); var tickers = LoadBootstrapTickers(); Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!); File.WriteAllText(outputPath, JsonSerializer.Serialize(new { gate = "PASS", generated_at_utc = DateTimeOffset.UtcNow, bootstrap = "collection-scheduling-ready", ticker_count = tickers.Count, tickers }, new JsonSerializerOptions { WriteIndented = true })); _logger.LogInformation("Collection bootstrap artifact written to {Path}", outputPath); } catch (Exception ex) { _logger.LogWarning(ex, "Collection bootstrap artifact generation failed"); } return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; private List LoadBootstrapTickers() { try { var jsonPath = FindGatherTradingDataJson(); if (jsonPath is null) { return ["005930"]; } var data = _parser.ParseGatherTradingData(jsonPath); return data .Select(row => row.TryGetValue("Ticker", out var value) ? value?.ToString()?.Trim('"') : null) .Where(ticker => !string.IsNullOrWhiteSpace(ticker)) .Distinct() .Take(10) .ToList()!; } catch { return ["005930"]; } } private static string FindRepoRoot() { var current = new DirectoryInfo(AppContext.BaseDirectory); while (current != null) { if (Directory.Exists(Path.Combine(current.FullName, ".git"))) { return current.FullName; } current = current.Parent; } return Directory.GetCurrentDirectory(); } private static string? FindGatherTradingDataJson() { var current = new DirectoryInfo(AppContext.BaseDirectory); while (current != null) { var candidate = Path.Combine(current.FullName, "GatherTradingData.json"); if (File.Exists(candidate)) { return candidate; } current = current.Parent; } return null; } }