From b8c7f9efdb1818acd5749f3be5eb07888438257f Mon Sep 17 00:00:00 2001 From: Wickedness Date: Tue, 9 Jun 2026 14:31:00 +0900 Subject: [PATCH] Add mapping profile validation --- TornadoAce_CJOnStyle/Domain/SceneModels.cs | 7 +++ .../Services/PocAutomationEngine.cs | 58 +++++++++++++++++++ .../ViewModels/MainViewModel.cs | 20 ++++++- .../ViewModels/ShellModels.cs | 7 +++ TornadoAce_CJOnStyle/Views/MainPage.xaml | 51 ++++++++++++++++ docs/poc-requirements.md | 3 +- 6 files changed, 144 insertions(+), 2 deletions(-) diff --git a/TornadoAce_CJOnStyle/Domain/SceneModels.cs b/TornadoAce_CJOnStyle/Domain/SceneModels.cs index 2bbae93..f9c461e 100644 --- a/TornadoAce_CJOnStyle/Domain/SceneModels.cs +++ b/TornadoAce_CJOnStyle/Domain/SceneModels.cs @@ -14,6 +14,13 @@ namespace TornadoAce_CJOnStyle.Domain string Attribute, string Rule); + public sealed record MappingValidationResult( + string CheckId, + string Title, + string Status, + string Detail, + string Severity); + public sealed record SceneDataSnapshot( string SceneName, string TemplateId, diff --git a/TornadoAce_CJOnStyle/Services/PocAutomationEngine.cs b/TornadoAce_CJOnStyle/Services/PocAutomationEngine.cs index 66b20eb..54abdc1 100644 --- a/TornadoAce_CJOnStyle/Services/PocAutomationEngine.cs +++ b/TornadoAce_CJOnStyle/Services/PocAutomationEngine.cs @@ -28,6 +28,49 @@ namespace TornadoAce_CJOnStyle.Services return BuildSnapshot(cueSheet, template, BuildVisibleTextObjects(cueSheet, template)); } + public IReadOnlyList ValidateMappingProfile(SceneTemplateDefinition template) + { + var requiredFields = new[] { "programTitle", "productName", "priceText", "compositionText", "promotionText" }; + var supportedAttributes = new[] { "Text", "Font", "Color", "Style", "Image" }; + var mappedFields = template.Mappings + .Select(mapping => mapping.CueSheetField) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + var missingFields = requiredFields + .Where(field => !mappedFields.Contains(field)) + .ToArray(); + var duplicateSceneObjects = template.Mappings + .GroupBy(mapping => mapping.SceneObjectName, StringComparer.OrdinalIgnoreCase) + .Where(group => group.Count() > 1) + .Select(group => group.Key) + .ToArray(); + var unsupportedAttributes = template.Mappings + .Where(mapping => !supportedAttributes.Contains(mapping.Attribute, StringComparer.OrdinalIgnoreCase)) + .Select(mapping => $"{mapping.SceneObjectName}:{mapping.Attribute}") + .ToArray(); + + return + [ + BuildValidationResult( + "MAP-01", + "필수 큐시트 필드", + missingFields.Length == 0, + missingFields.Length == 0 ? "5개 필드 매핑 완료" : $"누락: {string.Join(", ", missingFields)}", + "High"), + BuildValidationResult( + "MAP-02", + "Tornado 객체 중복", + duplicateSceneObjects.Length == 0, + duplicateSceneObjects.Length == 0 ? "중복 없음" : $"중복: {string.Join(", ", duplicateSceneObjects)}", + "High"), + BuildValidationResult( + "MAP-03", + "지원 속성", + unsupportedAttributes.Length == 0, + unsupportedAttributes.Length == 0 ? "Text 속성 기준 통과" : $"확인: {string.Join(", ", unsupportedAttributes)}", + "Medium") + ]; + } + public ErpScheduleRecord? FindSchedule(CueSheetRecord cueSheet) { return workspace.ErpSchedules @@ -148,6 +191,21 @@ namespace TornadoAce_CJOnStyle.Services return $@"D:\CJOnStyle\Products\{directoryName}\main.png"; } + private static MappingValidationResult BuildValidationResult( + string checkId, + string title, + bool isPassed, + string detail, + string failureSeverity) + { + return new MappingValidationResult( + checkId, + title, + isPassed ? "통과" : "확인", + detail, + isPassed ? "Low" : failureSeverity); + } + private static bool IsImportantSceneObject(string sceneObjectName) { return sceneObjectName.Contains("PRICE", StringComparison.OrdinalIgnoreCase) diff --git a/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs b/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs index ff2d6a2..66a5681 100644 --- a/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs +++ b/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs @@ -44,6 +44,7 @@ namespace TornadoAce_CJOnStyle.ViewModels LoadStatusCards(); LoadPipelineSteps(); LoadFieldMappings(); + LoadMappingValidation(); LoadEndpoints(); LoadRequirementCoverage(); LoadScheduleMatches(); @@ -126,6 +127,8 @@ namespace TornadoAce_CJOnStyle.ViewModels public ObservableCollection FieldMappings { get; } = []; + public ObservableCollection MappingValidations { get; } = []; + public ObservableCollection Endpoints { get; } = []; public ObservableCollection RequirementCoverage { get; } = []; @@ -246,6 +249,21 @@ namespace TornadoAce_CJOnStyle.ViewModels } } + private void LoadMappingValidation() + { + MappingValidations.Clear(); + + foreach (var validation in automationEngine.ValidateMappingProfile(workspace.ActiveTemplate)) + { + MappingValidations.Add(new MappingValidationViewModel( + validation.CheckId, + validation.Title, + validation.Status, + validation.Detail, + SeverityBrush(validation.Severity))); + } + } + private void LoadEndpoints() { Endpoints.Add(new IntegrationEndpointViewModel("큐시트 플랫폼", "GET /api/cuesheets/{broadcastDate}", "AI ON Studio", "Mock", Brush(0xFF45D6D1))); @@ -260,7 +278,7 @@ namespace TornadoAce_CJOnStyle.ViewModels RequirementCoverage.Add(new PocRequirementViewModel("RFP-02", "ERP 편성 연동", "PoC", "편성 JSON 매칭", Brush(0xFF37D67A))); RequirementCoverage.Add(new PocRequirementViewModel("RFP-03", "Data Field Mapping", "PoC", "Scene 스냅샷 생성", Brush(0xFF37D67A))); RequirementCoverage.Add(new PocRequirementViewModel("RFP-04", "Tornado Scene API", "PoC", "Text/Image 추출 샘플", Brush(0xFF37D67A))); - RequirementCoverage.Add(new PocRequirementViewModel("RFP-05", "관리자 매핑 기능", "초안", "매핑표 UI", Brush(0xFFB48CFF))); + RequirementCoverage.Add(new PocRequirementViewModel("RFP-05", "관리자 매핑 기능", "PoC", "프로파일 검증", Brush(0xFF37D67A))); RequirementCoverage.Add(new PocRequirementViewModel("RFP-06", "1·2단계 자동 검수", "PoC", "필드 상세 Diff + LLM 후보", Brush(0xFF45D6D1))); } diff --git a/TornadoAce_CJOnStyle/ViewModels/ShellModels.cs b/TornadoAce_CJOnStyle/ViewModels/ShellModels.cs index b751b8a..ad679ab 100644 --- a/TornadoAce_CJOnStyle/ViewModels/ShellModels.cs +++ b/TornadoAce_CJOnStyle/ViewModels/ShellModels.cs @@ -50,6 +50,13 @@ namespace TornadoAce_CJOnStyle.ViewModels string BindingRule, string Attribute); + public sealed record MappingValidationViewModel( + string CheckId, + string Title, + string Status, + string Detail, + SolidColorBrush StatusBrush); + public sealed record IntegrationEndpointViewModel( string Name, string Endpoint, diff --git a/TornadoAce_CJOnStyle/Views/MainPage.xaml b/TornadoAce_CJOnStyle/Views/MainPage.xaml index beb57c5..9dcde4b 100644 --- a/TornadoAce_CJOnStyle/Views/MainPage.xaml +++ b/TornadoAce_CJOnStyle/Views/MainPage.xaml @@ -340,6 +340,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/poc-requirements.md b/docs/poc-requirements.md index ea89223..30266bb 100644 --- a/docs/poc-requirements.md +++ b/docs/poc-requirements.md @@ -17,7 +17,7 @@ | RFP-02 | ERP 편성데이터 연동 | JSON Mock 편성 데이터 매칭 구현 | `Data/PocWorkspace.json`, `ErpScheduleRecord`, `ScheduleMatches` | | RFP-03 | CG 포맷 제작 자동화, Data Field Mapping | 큐시트 필드와 Scene 객체 매핑 구현 | `SceneTemplateDefinition`, `PocAutomationEngine.GenerateScene` | | RFP-04 | Tornado Scene API | Scene visible Text/Image 스냅샷 샘플 구현 | `SceneDataSnapshot.ToPreviewJson` | -| RFP-05 | 큐시트 데이터와 Tornado Scene 연동 관리자 기능 | 매핑표 UI 초안 구현 | `FieldMappings` 화면 | +| RFP-05 | 큐시트 데이터와 Tornado Scene 연동 관리자 기능 | JSON 매핑 프로파일과 검증 UI 구현 | `FieldMappings`, `MappingValidations`, `ValidateMappingProfile` | | RFP-06 | 자동 검수 1단계 Text Diffing | 필드별 기대값/실제값/일치율 비교 구현 | `PocAutomationEngine.InspectWorkspace`, `InspectionFieldResult` | | RFP-07 | 자동 검수 2단계 LLM 중요도 피드백 | LLM 검토 후보 분류와 제작자 상세 UI 구현 | 검수 결과 `Severity`, `Decision`, `InspectionDetails` | | RFP-08 | 제작자가 결과를 볼 수 있는 GUI | WinUI 3 운영 콘솔 초안 구현 | `MainPage.xaml` | @@ -33,6 +33,7 @@ ## 다음 구현 후보 - 실제 큐시트/ERP API 응답 DTO와 현재 JSON seed 스키마를 대조한다. +- 관리자용 매핑 프로파일 편집/저장 권한 정책을 정한다. - Tornado2/Tornado3 어댑터를 동일 인터페이스로 유지하고, 버전별 구현체를 분리한다. - Scene 내부 비노출 객체를 구분할 메타데이터 규칙을 정의한다. - 1차 검수 실패 사유를 필드 단위로 누적하고 LLM 요청 후보로 전달한다.