feat(report): 운전모드에 펌프 가동(RUN/STOP) 반영 + /live 최신 window만

- 펌프 게이트: 펌프 정지(state STOP/TRIP/FAULT) → 잔류 유량과 무관하게 idle.
  펌프 가동(L-RUN=1/R-RUN=5) → 흐름 기반(normal/cleaning/drawdown). DetectModeAsync 통합.
  인디케이터 펌프 = Report:Pumps:{col} 또는 제품펌프 P-{제품번호} 유도. realtime_table 현재 상태로 판정.
- /live: DISTINCT ON (column_id,kpi) 최신 window_start만 반환 — 과거 날짜 잔여 행이
  stale state로 표시되던 버그 수정.

검증: C-6111 펌프RUN→normal, C-6211 펌프STOP→idle(잔류유량 12.8 있어도). 펌프 상태값
5=R-RUN/1=L-RUN/0=L-STOP (tag_metadata state 라벨 확정). /live 컬럼당 state 단일.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
windpacer
2026-06-16 21:41:29 +09:00
parent 333d5f2a7b
commit cdf2719a5e
4 changed files with 39 additions and 8 deletions

View File

@@ -33,9 +33,11 @@ public class ReportController : ControllerBase
var conn = _db.Database.GetDbConnection();
if (conn.State != ConnectionState.Open) await conn.OpenAsync(ct);
await using var cmd = conn.CreateCommand();
cmd.CommandText = @"SELECT column_id, kpi, value, unit, state, excluded_min, status, window_start, updated_at
// (column_id,kpi)별 최신 window_start만 — 과거 날짜 잔여 행으로 인한 stale 표시 방지
cmd.CommandText = @"SELECT DISTINCT ON (column_id, kpi)
column_id, kpi, value, unit, state, excluded_min, status, window_start, updated_at
FROM hc900.live_kpi" + (column == null ? "" : " WHERE column_id=@col") +
" ORDER BY column_id, kpi";
" ORDER BY column_id, kpi, window_start DESC";
if (column != null) { var p = cmd.CreateParameter(); p.ParameterName = "@col"; p.Value = column; cmd.Parameters.Add(p); }
var items = new List<object>();
await using var rd = await cmd.ExecuteReaderAsync(ct);

View File

@@ -91,6 +91,10 @@
"C-9111": "C-10111",
"C-10111": "C-9111"
},
"Pumps": {
"C-6111": [ "P-6102", "P-6118" ],
"C-6211": [ "P-6201", "P-6218" ]
},
"Historian": {
"Enabled": true,
"IntervalSeconds": 1,

View File

@@ -157,13 +157,26 @@ CREATE TABLE IF NOT EXISTS hc900.kpi_alert (
feed = rd.IsDBNull(2) ? null : rd.GetDouble(2);
}
}
if (prod is null && feed is null) return ("no_data", feed);
// 펌프 가동 여부(realtime_table 현재 상태) — 정지면 잔류 흐름과 무관하게 idle.
var pumps = _map.PumpTags(col);
bool pumpKnown = pumps.Count > 0, pumpRunning = false;
if (pumpKnown)
{
await using var pq = conn.CreateCommand();
pq.CommandText = "SELECT count(*) FROM hc900.realtime_table WHERE tagname = ANY(@p) AND livevalue IN ('1','5','L-RUN','R-RUN')";
var pp = pq.CreateParameter(); pp.ParameterName = "@p"; pp.Value = pumps.ToArray(); pq.Parameters.Add(pp);
pumpRunning = Convert.ToInt64(await pq.ExecuteScalarAsync(ct)) > 0;
}
if (prod is null && feed is null && !pumpRunning) return ("no_data", feed);
bool fLow = (feed ?? 0) < cl.FeedMin, pLow = (prod ?? 0) < cl.ProductMin, vHigh = vac.HasValue && vac > cl.VacMax;
string mode = (fLow && pLow) ? "idle" // 아무것도 안 흐름 = 진짜 미가동
: vHigh ? "cleaning" // 진공 깨짐 = 세정/비운전
: (fLow) ? "drawdown" // feed≈0 + product 흐름 = 인벤토리 인출
: (pLow) ? "cleaning" // feed 흐름 + product≈0 = 세정/startup
: "normal"; // 둘 다 흐름 + 진공 정상
string mode =
(pumpKnown && !pumpRunning) ? "idle" // ★펌프 정지 = 셧다운(흐름 잔류 무관)
: (fLow && pLow) ? (pumpRunning ? "normal" : "idle") // 펌프 도는데 흐름 = 가동(저율/startup)
: vHigh ? "cleaning"
: (fLow) ? "drawdown"
: (pLow) ? "cleaning"
: "normal";
return (mode, feed);
}

View File

@@ -73,6 +73,18 @@ public sealed class ReportColumnMap
/// <summary>컬럼간 이송 라인 상대 컬럼(있으면). 예: C-9111↔C-10111. 없으면 null(=순수 인벤토리 인출).</summary>
public string? TransferPartner(string column) => _config[$"Report:Transfer:{column}"];
/// <summary>가동 판정용 펌프 상태태그(.PV). Report:Pumps:{col} 우선, 없으면 제품펌프 P-{제품번호} 유도.</summary>
public IReadOnlyList<string> PumpTags(string column)
{
var configured = _config.GetSection($"Report:Pumps:{column}").Get<string[]>();
if (configured is { Length: > 0 })
return configured.Select(t => t.Contains('.') ? t : t + ".PV").ToList();
var prod = _config[$"SteamAdvisor:Columns:{column}:Product"]; // 예: FICQ-6118.PV
if (string.IsNullOrWhiteSpace(prod)) return Array.Empty<string>();
var num = StripAttr(prod!).Split('-').Last(); // 6118
return new[] { $"P-{num}.PV" }; // 제품펌프
}
/// <summary>동특성 대상 루프(하부온도 TICA-*A): PV vs OP(스팀밸브). 밸브 stiction/hunting 진단용.</summary>
public bool TryResolveDynamics(string column, out string? pvTag, out string? opTag)
{