Add mapping profile validation

This commit is contained in:
2026-06-09 14:31:00 +09:00
parent 8603f2cf31
commit b8c7f9efdb
6 changed files with 144 additions and 2 deletions

View File

@@ -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,

View File

@@ -28,6 +28,49 @@ namespace TornadoAce_CJOnStyle.Services
return BuildSnapshot(cueSheet, template, BuildVisibleTextObjects(cueSheet, template));
}
public IReadOnlyList<MappingValidationResult> 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)

View File

@@ -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<FieldMappingViewModel> FieldMappings { get; } = [];
public ObservableCollection<MappingValidationViewModel> MappingValidations { get; } = [];
public ObservableCollection<IntegrationEndpointViewModel> Endpoints { get; } = [];
public ObservableCollection<PocRequirementViewModel> 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)));
}

View File

@@ -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,

View File

@@ -340,6 +340,57 @@
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Border
Height="1"
Background="{StaticResource PanelStrokeBrush}" />
<Grid>
<TextBlock Style="{StaticResource SectionTitleTextStyle}" Text="관리자 매핑 검증" />
<TextBlock
HorizontalAlignment="Right"
Style="{StaticResource LabelTextStyle}"
Text="Profile Check" />
</Grid>
<ItemsControl ItemsSource="{x:Bind ViewModel.MappingValidations, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:MappingValidationViewModel">
<Grid
Margin="0,0,0,8"
ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="58" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
FontFamily="Cascadia Mono"
FontSize="12"
Foreground="{StaticResource TextMutedBrush}"
Text="{x:Bind CheckId}" />
<StackPanel Grid.Column="1" Spacing="2">
<TextBlock
FontFamily="Bahnschrift SemiBold"
Foreground="{StaticResource TextPrimaryBrush}"
Text="{x:Bind Title}" />
<TextBlock
Style="{StaticResource LabelTextStyle}"
Text="{x:Bind Detail}"
TextTrimming="CharacterEllipsis" />
</StackPanel>
<Border
Grid.Column="2"
Padding="8,3"
Background="{x:Bind StatusBrush}"
CornerRadius="8">
<TextBlock
FontFamily="Bahnschrift SemiBold"
FontSize="12"
Foreground="#071013"
Text="{x:Bind Status}" />
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>

View File

@@ -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 요청 후보로 전달한다.