Persist PoC run audit logs
This commit is contained in:
66
TornadoAce_CJOnStyle/Services/PocRunLogWriter.cs
Normal file
66
TornadoAce_CJOnStyle/Services/PocRunLogWriter.cs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ namespace TornadoAce_CJOnStyle.ViewModels
|
|||||||
{
|
{
|
||||||
private readonly AutomationWorkspace workspace;
|
private readonly AutomationWorkspace workspace;
|
||||||
private readonly PocAutomationEngine automationEngine;
|
private readonly PocAutomationEngine automationEngine;
|
||||||
|
private readonly PocRunLogWriter pocRunLogWriter = new();
|
||||||
private int selectedCueSheetIndex;
|
private int selectedCueSheetIndex;
|
||||||
private CueSheetRecord activeCueSheet = null!;
|
private CueSheetRecord activeCueSheet = null!;
|
||||||
private ErpScheduleRecord? activeSchedule;
|
private ErpScheduleRecord? activeSchedule;
|
||||||
@@ -40,6 +41,7 @@ namespace TornadoAce_CJOnStyle.ViewModels
|
|||||||
LaunchMessage = "TornadoAce";
|
LaunchMessage = "TornadoAce";
|
||||||
LlmPolicyText = "사내 LLM 우선, 외부 서비스는 정책 승인 후";
|
LlmPolicyText = "사내 LLM 우선, 외부 서비스는 정책 승인 후";
|
||||||
LlmPolicyDetail = "1차 Text Diffing 실패 건만 2차 LLM 판정으로 이동하고, 가격·구성·방송시간 변경은 높은 중요도로 표시합니다.";
|
LlmPolicyDetail = "1차 Text Diffing 실패 건만 2차 LLM 판정으로 이동하고, 가격·구성·방송시간 변경은 높은 중요도로 표시합니다.";
|
||||||
|
PocRunLogText = "PoC 로그 대기";
|
||||||
|
|
||||||
LoadStatusCards();
|
LoadStatusCards();
|
||||||
LoadPipelineSteps();
|
LoadPipelineSteps();
|
||||||
@@ -63,6 +65,7 @@ namespace TornadoAce_CJOnStyle.ViewModels
|
|||||||
private string sceneSnapshotText = string.Empty;
|
private string sceneSnapshotText = string.Empty;
|
||||||
private string llmPolicyText = string.Empty;
|
private string llmPolicyText = string.Empty;
|
||||||
private string llmPolicyDetail = string.Empty;
|
private string llmPolicyDetail = string.Empty;
|
||||||
|
private string pocRunLogText = string.Empty;
|
||||||
private string loginOperatorName = string.Empty;
|
private string loginOperatorName = string.Empty;
|
||||||
private string launchMessage = string.Empty;
|
private string launchMessage = string.Empty;
|
||||||
private Visibility loginScreenVisibility = Visibility.Visible;
|
private Visibility loginScreenVisibility = Visibility.Visible;
|
||||||
@@ -123,6 +126,12 @@ namespace TornadoAce_CJOnStyle.ViewModels
|
|||||||
set => SetProperty(ref llmPolicyDetail, value);
|
set => SetProperty(ref llmPolicyDetail, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string PocRunLogText
|
||||||
|
{
|
||||||
|
get => pocRunLogText;
|
||||||
|
set => SetProperty(ref pocRunLogText, value);
|
||||||
|
}
|
||||||
|
|
||||||
public ObservableCollection<StatusCardViewModel> StatusCards { get; } = [];
|
public ObservableCollection<StatusCardViewModel> StatusCards { get; } = [];
|
||||||
|
|
||||||
public ObservableCollection<PipelineStepViewModel> PipelineSteps { get; } = [];
|
public ObservableCollection<PipelineStepViewModel> PipelineSteps { get; } = [];
|
||||||
@@ -235,6 +244,7 @@ namespace TornadoAce_CJOnStyle.ViewModels
|
|||||||
private void RunPoc()
|
private void RunPoc()
|
||||||
{
|
{
|
||||||
var result = automationEngine.RunFullPoc(selectedCueSheetIndex, workspace.ActiveTemplate);
|
var result = automationEngine.RunFullPoc(selectedCueSheetIndex, workspace.ActiveTemplate);
|
||||||
|
var logPath = pocRunLogWriter.Write(result);
|
||||||
activeCueSheet = result.Selection.Record;
|
activeCueSheet = result.Selection.Record;
|
||||||
activeSchedule = result.Schedule;
|
activeSchedule = result.Schedule;
|
||||||
activeScene = result.GeneratedScene;
|
activeScene = result.GeneratedScene;
|
||||||
@@ -243,6 +253,7 @@ namespace TornadoAce_CJOnStyle.ViewModels
|
|||||||
ActiveTemplateName = workspace.ActiveTemplate.TemplateName;
|
ActiveTemplateName = workspace.ActiveTemplate.TemplateName;
|
||||||
InspectionSummary = FormatInspectionSummary(result.InspectionSummary);
|
InspectionSummary = FormatInspectionSummary(result.InspectionSummary);
|
||||||
OperatorMessage = FormatPocRunSummary(result);
|
OperatorMessage = FormatPocRunSummary(result);
|
||||||
|
PocRunLogText = $"로그 저장: {Path.GetFileName(logPath)}";
|
||||||
|
|
||||||
StatusCards[0].Metric = result.Selection.TotalCount.ToString();
|
StatusCards[0].Metric = result.Selection.TotalCount.ToString();
|
||||||
StatusCards[0].Detail = $"ERP {FormatScheduleStatus(result.Schedule)}";
|
StatusCards[0].Detail = $"ERP {FormatScheduleStatus(result.Schedule)}";
|
||||||
|
|||||||
@@ -116,41 +116,47 @@
|
|||||||
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Orientation="Horizontal"
|
HorizontalAlignment="Right"
|
||||||
Spacing="10"
|
Spacing="8"
|
||||||
VerticalAlignment="Top">
|
VerticalAlignment="Top">
|
||||||
<Button
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||||
Command="{x:Bind ViewModel.RunPocCommand}"
|
<Button
|
||||||
Style="{StaticResource PrimaryActionButtonStyle}">
|
Command="{x:Bind ViewModel.RunPocCommand}"
|
||||||
<StackPanel Orientation="Horizontal" Spacing="7">
|
Style="{StaticResource PrimaryActionButtonStyle}">
|
||||||
<SymbolIcon Symbol="Play" />
|
<StackPanel Orientation="Horizontal" Spacing="7">
|
||||||
<TextBlock Text="PoC 실행" />
|
<SymbolIcon Symbol="Play" />
|
||||||
</StackPanel>
|
<TextBlock Text="PoC 실행" />
|
||||||
</Button>
|
</StackPanel>
|
||||||
<Button
|
</Button>
|
||||||
Command="{x:Bind ViewModel.RefreshCueSheetCommand}"
|
<Button
|
||||||
Style="{StaticResource GhostActionButtonStyle}">
|
Command="{x:Bind ViewModel.RefreshCueSheetCommand}"
|
||||||
<StackPanel Orientation="Horizontal" Spacing="7">
|
Style="{StaticResource GhostActionButtonStyle}">
|
||||||
<SymbolIcon Symbol="Refresh" />
|
<StackPanel Orientation="Horizontal" Spacing="7">
|
||||||
<TextBlock Text="큐시트 동기화" />
|
<SymbolIcon Symbol="Refresh" />
|
||||||
</StackPanel>
|
<TextBlock Text="큐시트 동기화" />
|
||||||
</Button>
|
</StackPanel>
|
||||||
<Button
|
</Button>
|
||||||
Command="{x:Bind ViewModel.GenerateSceneCommand}"
|
<Button
|
||||||
Style="{StaticResource GhostActionButtonStyle}">
|
Command="{x:Bind ViewModel.GenerateSceneCommand}"
|
||||||
<StackPanel Orientation="Horizontal" Spacing="7">
|
Style="{StaticResource GhostActionButtonStyle}">
|
||||||
<SymbolIcon Symbol="Play" />
|
<StackPanel Orientation="Horizontal" Spacing="7">
|
||||||
<TextBlock Text="Scene 생성" />
|
<SymbolIcon Symbol="Play" />
|
||||||
</StackPanel>
|
<TextBlock Text="Scene 생성" />
|
||||||
</Button>
|
</StackPanel>
|
||||||
<Button
|
</Button>
|
||||||
Command="{x:Bind ViewModel.RunInspectionCommand}"
|
<Button
|
||||||
Style="{StaticResource GhostActionButtonStyle}">
|
Command="{x:Bind ViewModel.RunInspectionCommand}"
|
||||||
<StackPanel Orientation="Horizontal" Spacing="7">
|
Style="{StaticResource GhostActionButtonStyle}">
|
||||||
<SymbolIcon Symbol="Accept" />
|
<StackPanel Orientation="Horizontal" Spacing="7">
|
||||||
<TextBlock Text="검수 실행" />
|
<SymbolIcon Symbol="Accept" />
|
||||||
</StackPanel>
|
<TextBlock Text="검수 실행" />
|
||||||
</Button>
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Style="{StaticResource LabelTextStyle}"
|
||||||
|
Text="{x:Bind ViewModel.PocRunLogText, Mode=OneWay}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
- `Domain`: 큐시트, Scene 템플릿, Scene 데이터 스냅샷, 검수 결과 모델.
|
- `Domain`: 큐시트, Scene 템플릿, Scene 데이터 스냅샷, 검수 결과 모델.
|
||||||
- `Data`: 큐시트, ERP 편성, Scene 템플릿, Scene 추출값을 담은 JSON 기반 PoC 샘플.
|
- `Data`: 큐시트, ERP 편성, Scene 템플릿, Scene 추출값을 담은 JSON 기반 PoC 샘플.
|
||||||
- `Services`: 실제 API/Tornado SDK 연결을 받을 인터페이스, JSON 로더, 목 워크스페이스, Mock Tornado adapter.
|
- `Services`: 실제 API/Tornado SDK 연결을 받을 인터페이스, JSON 로더, 목 워크스페이스, Mock Tornado adapter, PoC 실행 로그 저장.
|
||||||
- `ViewModels`: 운영 콘솔에 표시할 상태 카드, 파이프라인, 매핑표, 검수 큐.
|
- `ViewModels`: 운영 콘솔에 표시할 상태 카드, 파이프라인, 매핑표, 검수 큐.
|
||||||
- `Views`: WinUI 3 기반 첫 화면. 예제 프로젝트와 같은 데스크톱 앱 방향으로 유지.
|
- `Views`: WinUI 3 기반 첫 화면. 예제 프로젝트와 같은 데스크톱 앱 방향으로 유지.
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
1. Windows 데스크톱 앱으로 실행 가능한 WinUI 3 빌드가 유지된다.
|
1. Windows 데스크톱 앱으로 실행 가능한 WinUI 3 빌드가 유지된다.
|
||||||
2. CJ OnStyle/TornadoAce 정체성이 드러나는 로그인 도입 화면과 앱 아이콘이 적용된다.
|
2. CJ OnStyle/TornadoAce 정체성이 드러나는 로그인 도입 화면과 앱 아이콘이 적용된다.
|
||||||
3. `PoC 실행`으로 큐시트 선택, ERP 매칭, Scene 스냅샷 생성, 자동 검수, LLM 후보 생성이 한 번에 갱신된다.
|
3. `PoC 실행`으로 큐시트 선택, ERP 매칭, Scene 스냅샷 생성, 자동 검수, LLM 후보 생성, 실행 로그 저장이 한 번에 갱신된다.
|
||||||
4. RFP 구축 범위가 화면 또는 문서에서 추적 가능해야 한다.
|
4. RFP 구축 범위가 화면 또는 문서에서 추적 가능해야 한다.
|
||||||
5. 추후 실제 큐시트 API, ERP API, Tornado SDK, LLM 서비스로 교체할 인터페이스 경계가 유지된다.
|
5. 추후 실제 큐시트 API, ERP API, Tornado SDK, LLM 서비스로 교체할 인터페이스 경계가 유지된다.
|
||||||
|
|
||||||
@@ -37,4 +37,4 @@
|
|||||||
- 실제 Tornado2 SDK 구현체와 Tornado3 SDK 구현체의 메서드 차이를 `ITornadoSceneAdapter`에 맞춰 정리한다.
|
- 실제 Tornado2 SDK 구현체와 Tornado3 SDK 구현체의 메서드 차이를 `ITornadoSceneAdapter`에 맞춰 정리한다.
|
||||||
- Scene 내부 비노출 객체를 구분할 메타데이터 규칙을 정의한다.
|
- Scene 내부 비노출 객체를 구분할 메타데이터 규칙을 정의한다.
|
||||||
- LLM 응답 스키마와 감사 로그 보관 정책을 실제 사내 정책에 맞춘다.
|
- LLM 응답 스키마와 감사 로그 보관 정책을 실제 사내 정책에 맞춘다.
|
||||||
- PoC 실행 결과를 파일 또는 운영 로그로 저장하는 정책을 정한다.
|
- PoC 실행 로그의 보관 기간, 열람 권한, 운영 로그 연동 방식을 정한다.
|
||||||
|
|||||||
Reference in New Issue
Block a user