using System.Globalization; using ExperionCrawler.Core.Application.Feedforward; using ExperionCrawler.Core.Application.Interfaces; namespace ExperionCrawler.Infrastructure.Control; /// /// WP3-C: 라이브 데이터 수집 + FeedRampCalculator 호출. 읽기 전용(쓰기 없음). /// Sim Override(WP0) 활성 시 해당 태그는 override값 우선. extras 태그는 C-6111 스코프 하드코딩(작업지시서 §5). /// public sealed class FeedRampAdvisorService { private readonly IFeedforwardConfigStore _cfgStore; private readonly IExperionDbService _db; private readonly ISimOverrideStore _sim; public FeedRampAdvisorService(IFeedforwardConfigStore cfgStore, IExperionDbService db, ISimOverrideStore sim) { _cfgStore = cfgStore; _db = db; _sim = sim; } public async Task ComputeAsync( int columnId, double targetFeed, double deltaIAllow, double sensibleGain, double feedTempRef, double floodLimit, double n, CancellationToken ct = default) { var cfg = (await _cfgStore.LoadAllAsync(ct)).FirstOrDefault(c => c.Id == columnId); if (cfg is null) return null; static string PvTag(string baseTag) { var t = baseTag.ToLowerInvariant(); return t.EndsWith(".pv") ? t : t + ".pv"; } string picaTag = PvTag(cfg.PressureTag ?? "pica-6111"); const string piTag = "pi-6111b.pv", tiTag = "ti-6103.pv", steamTag = "ficq-6115.pv", opTag = "tica-6111a.op"; var tags = new List { PvTag(cfg.FeedTag), picaTag, piTag, tiTag, steamTag, opTag }; tags.AddRange(cfg.Streams.Select(s => PvTag(s.FlowTag))); var rows = (await _db.GetRealtimeRecordsByTagNamesAsync(tags)) .ToDictionary(r => r.TagName.ToLowerInvariant(), r => r); bool TryRead(string tag, out double v) { tag = tag.ToLowerInvariant(); if (_sim.Enabled && _sim.TryGet(tag, out v)) return true; // override(신선) if (rows.TryGetValue(tag, out var r) && r.LiveValue is not null && double.TryParse(r.LiveValue, NumberStyles.Float, CultureInfo.InvariantCulture, out v) && (DateTime.UtcNow - r.Timestamp.ToUniversalTime()).TotalSeconds <= cfg.StaleSec) return true; v = double.NaN; return false; } TagSample Sample(string baseTag) { var tag = PvTag(baseTag); bool ok = TryRead(tag, out var v); return new TagSample(tag, ok ? v : double.NaN, ok, DateTime.UtcNow); } double? Opt(string tag) => TryRead(tag, out var v) ? v : (double?)null; var feed = Sample(cfg.FeedTag); var streams = cfg.Streams.ToDictionary(s => s.Key, s => Sample(s.FlowTag)); var pv = new PvSnapshot(feed, null, Array.Empty(), streams); var extra = new RampExtraInputs( Pica: Opt(picaTag), Pi6111b: Opt(piTag), Ti6103: Opt(tiTag), Fiq6115: Opt(steamTag), Op: Opt(opTag), FloodLimit: floodLimit, FloodExponentN: n); return FeedRampCalculator.Compute(cfg, pv, targetFeed, deltaIAllow, sensibleGain, feedTempRef, extra, _sim.Enabled); } }