diff --git a/TornadoAce_CJOnStyle/Services/PocRunLogWriter.cs b/TornadoAce_CJOnStyle/Services/PocRunLogWriter.cs new file mode 100644 index 0000000..b3ed89c --- /dev/null +++ b/TornadoAce_CJOnStyle/Services/PocRunLogWriter.cs @@ -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 MappingChecks, + IReadOnlyList AdapterChecks, + IReadOnlyList 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()); + } + } +} diff --git a/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs b/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs index 8acedb4..4ce5efa 100644 --- a/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs +++ b/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs @@ -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 StatusCards { get; } = []; public ObservableCollection 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)}"; diff --git a/TornadoAce_CJOnStyle/Views/MainPage.xaml b/TornadoAce_CJOnStyle/Views/MainPage.xaml index 9c0dcf9..5dae460 100644 --- a/TornadoAce_CJOnStyle/Views/MainPage.xaml +++ b/TornadoAce_CJOnStyle/Views/MainPage.xaml @@ -116,41 +116,47 @@ - - - - + + + + + + + diff --git a/docs/initial-architecture.md b/docs/initial-architecture.md index fbbf38b..00a943f 100644 --- a/docs/initial-architecture.md +++ b/docs/initial-architecture.md @@ -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 기반 첫 화면. 예제 프로젝트와 같은 데스크톱 앱 방향으로 유지. diff --git a/docs/poc-requirements.md b/docs/poc-requirements.md index 7a84eda..0e94f79 100644 --- a/docs/poc-requirements.md +++ b/docs/poc-requirements.md @@ -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 실행 로그의 보관 기간, 열람 권한, 운영 로그 연동 방식을 정한다.