PV-중앙값 근사 대신 유량 적산(.QV)으로 정확한 적분 메트릭 추가, 웹에서 바로 보기. - QV 메트릭 4종: production_total·yield_qv·energy_intensity_qv·mass_balance_closure. 적산 Δ 헬퍼(QvDeltaAsync)는 값 급감(reset/wrap)으로 구간분할 후 구간별 (마지막-처음) 합산 → gap·양자화 강건, DCS 일일적산과 동일 의미(노이즈 과대계상 없음). - 매핑은 appsettings SteamAdvisor:Columns 재사용(.QV 치환, 7컬럼). 폐합 스트림은 Report:Closure:C-6111(Feed + Outputs[제품·경비물·중비물]). - 웹 대시보드: GET /api/report/columns, /api/report/summary → 리포트 탭에서 컬럼·날짜 선택 시 물질수지 신뢰블록(IN/OUT/폐합%, 98~101% 색상) + 메트릭 표를 즉시 렌더. 엑셀 export와 병존. - 물리적 타당성 게이트: 수율>1.5·에너지원단위>5·≤0 → no_data+사유(garbage 차단). 검증(2026-05-15 C-6111): 생산 8455kg·수율 0.8739·에너지 0.7791·폐합 99.04% (feed 9675/out 9582), 17주 폐합 99.1~99.8%. C-9111 garbage 수율 → no_data 처리 확인. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
79 lines
3.6 KiB
C#
79 lines
3.6 KiB
C#
using Hc900Crawler.Core.Application.DTOs;
|
|
using Hc900Crawler.Core.Application.Interfaces;
|
|
using Hc900Crawler.Infrastructure.Reporting;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace Hc900Crawler.Web.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/report")]
|
|
public class ReportController : ControllerBase
|
|
{
|
|
private readonly IReportMetricService _metrics;
|
|
private readonly ReportFillService _fill;
|
|
private readonly IReportTemplateStore _store;
|
|
private readonly ReportColumnMap _map;
|
|
|
|
// 웹 대시보드 기본 메트릭 세트
|
|
private static readonly string[] SUMMARY_METRICS =
|
|
{ "production_total", "yield_qv", "energy_intensity_qv", "mass_balance_closure", "control_residual" };
|
|
|
|
public ReportController(IReportMetricService metrics, ReportFillService fill,
|
|
IReportTemplateStore store, ReportColumnMap map)
|
|
{ _metrics = metrics; _fill = fill; _store = store; _map = map; }
|
|
|
|
/// <summary>설정된 컬럼 목록(웹 UI 셀렉트용).</summary>
|
|
[HttpGet("columns")]
|
|
public IActionResult Columns()
|
|
=> Ok(_map.Columns().Select(c => new { Column = c, HasClosure = _map.HasClosure(c) }));
|
|
|
|
/// <summary>단건 메트릭(미리보기/디버그).</summary>
|
|
[HttpPost("metric")]
|
|
public async Task<IActionResult> Metric([FromBody] MetricRequestDto req, CancellationToken ct)
|
|
=> Ok(await _metrics.ComputeAsync(req, ct));
|
|
|
|
/// <summary>웹에서 바로 보기 — 한 컬럼·날짜의 전 메트릭을 한 번에.</summary>
|
|
[HttpGet("summary")]
|
|
public async Task<IActionResult> Summary(string column = "C-6111", DateTime? date = null,
|
|
string source = "history_table", int? sessionId = null, CancellationToken ct = default)
|
|
{
|
|
var d = (date ?? DateTime.UtcNow.AddHours(9).AddDays(-1)).Date; // 기본 = 어제(KST)
|
|
var results = new List<MetricResultDto>();
|
|
foreach (var m in SUMMARY_METRICS)
|
|
results.Add(await _metrics.ComputeAsync(new MetricRequestDto
|
|
{
|
|
Column = column, Metric = m, PeriodDateKst = d,
|
|
SourceTable = source, SessionId = sessionId
|
|
}, ct));
|
|
return Ok(new { Column = column, Date = d.ToString("yyyy-MM-dd"), Source = source, Metrics = results });
|
|
}
|
|
|
|
/// <summary>엑셀 템플릿 등록.</summary>
|
|
[HttpPost("template")]
|
|
public async Task<IActionResult> Upload([FromForm] IFormFile file, [FromForm] string name,
|
|
[FromForm] string? owner, CancellationToken ct)
|
|
{
|
|
if (file == null || file.Length == 0) return BadRequest(new { Error = "파일 없음" });
|
|
using var ms = new MemoryStream();
|
|
await file.CopyToAsync(ms, ct);
|
|
var id = await _store.CreateAsync(name, owner, ms.ToArray(), ct);
|
|
return Ok(new { Id = id });
|
|
}
|
|
|
|
/// <summary>★템플릿+날짜 → 채워진 xlsx 다운로드.</summary>
|
|
[HttpGet("generate")]
|
|
public async Task<IActionResult> Generate(int templateId, DateTime date,
|
|
string source = "history_table", int? sessionId = null, CancellationToken ct = default)
|
|
{
|
|
var tpl = await _store.GetBlobAsync(templateId, ct);
|
|
if (tpl == null) return NotFound(new { Error = $"템플릿 {templateId} 없음" });
|
|
|
|
var (xlsx, cells, status) = await _fill.FillAsync(tpl, date, source, sessionId, ct);
|
|
await _store.RecordRunAsync(templateId, "DAILY", date, source, status, cells, xlsx, ct);
|
|
|
|
Response.Headers["X-Report-Status"] = status;
|
|
var fname = $"report_{templateId}_{date:yyyyMMdd}.xlsx";
|
|
return File(xlsx, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fname);
|
|
}
|
|
}
|