Persist PoC run audit logs

This commit is contained in:
2026-06-09 14:47:26 +09:00
parent c7adde3df2
commit 8b5b5b52a5
5 changed files with 120 additions and 37 deletions

View File

@@ -0,0 +1,66 @@
using System.Text.Json;
namespace TornadoAce_CJOnStyle.Services
{
public sealed class PocRunLogWriter
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true
};
public string Write(PocRunResult result)
{
var logDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"TornadoAce_CJOnStyle",
"Logs");
Directory.CreateDirectory(logDirectory);
var fileName = $"{DateTime.Now:yyyyMMdd-HHmmss}-{SanitizeFileName(result.Selection.Record.CueSheetId)}.json";
var logPath = Path.Combine(logDirectory, fileName);
var entry = PocRunLogEntry.From(result);
File.WriteAllText(logPath, JsonSerializer.Serialize(entry, JsonOptions));
return logPath;
}
private static string SanitizeFileName(string value)
{
var invalidChars = Path.GetInvalidFileNameChars();
var chars = value.Select(character => invalidChars.Contains(character) ? '_' : character).ToArray();
return new string(chars);
}
}
public sealed record PocRunLogEntry(
DateTime CreatedAt,
string CueSheetId,
string ProgramTitle,
string? ErpProgramCode,
string SceneName,
double AverageMatchRate,
int ReviewCount,
int LlmRequestCount,
IReadOnlyList<string> MappingChecks,
IReadOnlyList<string> AdapterChecks,
IReadOnlyList<string> LlmAuditSummaries)
{
public static PocRunLogEntry From(PocRunResult result)
{
return new PocRunLogEntry(
DateTime.Now,
result.Selection.Record.CueSheetId,
result.Selection.Record.ProgramTitle,
result.Schedule?.ProgramCode,
result.GeneratedScene.SceneName,
result.InspectionSummary.AverageMatchRate,
result.InspectionSummary.ReviewCount,
result.LlmReviewRequests.Count,
result.MappingValidations.Select(check => $"{check.CheckId}:{check.Status}:{check.Detail}").ToArray(),
result.AdapterChecks.Select(check => $"{check.CheckId}:{check.Status}:{check.Evidence}").ToArray(),
result.LlmReviewRequests.Select(request => request.AuditSummary).ToArray());
}
}
}

View File

@@ -13,6 +13,7 @@ namespace TornadoAce_CJOnStyle.ViewModels
{
private readonly AutomationWorkspace workspace;
private readonly PocAutomationEngine automationEngine;
private readonly PocRunLogWriter pocRunLogWriter = new();
private int selectedCueSheetIndex;
private CueSheetRecord activeCueSheet = null!;
private ErpScheduleRecord? activeSchedule;
@@ -40,6 +41,7 @@ namespace TornadoAce_CJOnStyle.ViewModels
LaunchMessage = "TornadoAce";
LlmPolicyText = "사내 LLM 우선, 외부 서비스는 정책 승인 후";
LlmPolicyDetail = "1차 Text Diffing 실패 건만 2차 LLM 판정으로 이동하고, 가격·구성·방송시간 변경은 높은 중요도로 표시합니다.";
PocRunLogText = "PoC 로그 대기";
LoadStatusCards();
LoadPipelineSteps();
@@ -63,6 +65,7 @@ namespace TornadoAce_CJOnStyle.ViewModels
private string sceneSnapshotText = string.Empty;
private string llmPolicyText = string.Empty;
private string llmPolicyDetail = string.Empty;
private string pocRunLogText = string.Empty;
private string loginOperatorName = string.Empty;
private string launchMessage = string.Empty;
private Visibility loginScreenVisibility = Visibility.Visible;
@@ -123,6 +126,12 @@ namespace TornadoAce_CJOnStyle.ViewModels
set => SetProperty(ref llmPolicyDetail, value);
}
public string PocRunLogText
{
get => pocRunLogText;
set => SetProperty(ref pocRunLogText, value);
}
public ObservableCollection<StatusCardViewModel> StatusCards { get; } = [];
public ObservableCollection<PipelineStepViewModel> PipelineSteps { get; } = [];
@@ -235,6 +244,7 @@ namespace TornadoAce_CJOnStyle.ViewModels
private void RunPoc()
{
var result = automationEngine.RunFullPoc(selectedCueSheetIndex, workspace.ActiveTemplate);
var logPath = pocRunLogWriter.Write(result);
activeCueSheet = result.Selection.Record;
activeSchedule = result.Schedule;
activeScene = result.GeneratedScene;
@@ -243,6 +253,7 @@ namespace TornadoAce_CJOnStyle.ViewModels
ActiveTemplateName = workspace.ActiveTemplate.TemplateName;
InspectionSummary = FormatInspectionSummary(result.InspectionSummary);
OperatorMessage = FormatPocRunSummary(result);
PocRunLogText = $"로그 저장: {Path.GetFileName(logPath)}";
StatusCards[0].Metric = result.Selection.TotalCount.ToString();
StatusCards[0].Detail = $"ERP {FormatScheduleStatus(result.Schedule)}";

View File

@@ -116,9 +116,10 @@
<StackPanel
Grid.Column="1"
Orientation="Horizontal"
Spacing="10"
HorizontalAlignment="Right"
Spacing="8"
VerticalAlignment="Top">
<StackPanel Orientation="Horizontal" Spacing="10">
<Button
Command="{x:Bind ViewModel.RunPocCommand}"
Style="{StaticResource PrimaryActionButtonStyle}">
@@ -152,6 +153,11 @@
</StackPanel>
</Button>
</StackPanel>
<TextBlock
HorizontalAlignment="Right"
Style="{StaticResource LabelTextStyle}"
Text="{x:Bind ViewModel.PocRunLogText, Mode=OneWay}" />
</StackPanel>
</Grid>
<ItemsControl

View File

@@ -12,7 +12,7 @@
- `Domain`: 큐시트, Scene 템플릿, Scene 데이터 스냅샷, 검수 결과 모델.
- `Data`: 큐시트, ERP 편성, Scene 템플릿, Scene 추출값을 담은 JSON 기반 PoC 샘플.
- `Services`: 실제 API/Tornado SDK 연결을 받을 인터페이스, JSON 로더, 목 워크스페이스, Mock Tornado adapter.
- `Services`: 실제 API/Tornado SDK 연결을 받을 인터페이스, JSON 로더, 목 워크스페이스, Mock Tornado adapter, PoC 실행 로그 저장.
- `ViewModels`: 운영 콘솔에 표시할 상태 카드, 파이프라인, 매핑표, 검수 큐.
- `Views`: WinUI 3 기반 첫 화면. 예제 프로젝트와 같은 데스크톱 앱 방향으로 유지.

View File

@@ -26,7 +26,7 @@
1. Windows 데스크톱 앱으로 실행 가능한 WinUI 3 빌드가 유지된다.
2. CJ OnStyle/TornadoAce 정체성이 드러나는 로그인 도입 화면과 앱 아이콘이 적용된다.
3. `PoC 실행`으로 큐시트 선택, ERP 매칭, Scene 스냅샷 생성, 자동 검수, LLM 후보 생성이 한 번에 갱신된다.
3. `PoC 실행`으로 큐시트 선택, ERP 매칭, Scene 스냅샷 생성, 자동 검수, LLM 후보 생성, 실행 로그 저장이 한 번에 갱신된다.
4. RFP 구축 범위가 화면 또는 문서에서 추적 가능해야 한다.
5. 추후 실제 큐시트 API, ERP API, Tornado SDK, LLM 서비스로 교체할 인터페이스 경계가 유지된다.
@@ -37,4 +37,4 @@
- 실제 Tornado2 SDK 구현체와 Tornado3 SDK 구현체의 메서드 차이를 `ITornadoSceneAdapter`에 맞춰 정리한다.
- Scene 내부 비노출 객체를 구분할 메타데이터 규칙을 정의한다.
- LLM 응답 스키마와 감사 로그 보관 정책을 실제 사내 정책에 맞춘다.
- PoC 실행 결과를 파일 또는 운영 로그로 저장하는 정책을 정한다.
- PoC 실행 로그의 보관 기간, 열람 권한, 운영 로그 연동 방식을 정한다.