diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f19a8f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +bin/ +obj/ +.vs/ +.vscode/ + +*.user +*.rsuser +*.suo +*.userosscache +*.sln.docstates + +TestResults/ +*.nupkg +*.snupkg + +publish/ +artifacts/ diff --git a/TornadoAce_CJOnStyle.slnx b/TornadoAce_CJOnStyle.slnx new file mode 100644 index 0000000..deeec63 --- /dev/null +++ b/TornadoAce_CJOnStyle.slnx @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/TornadoAce_CJOnStyle/App.xaml b/TornadoAce_CJOnStyle/App.xaml new file mode 100644 index 0000000..4f551da --- /dev/null +++ b/TornadoAce_CJOnStyle/App.xaml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TornadoAce_CJOnStyle/App.xaml.cs b/TornadoAce_CJOnStyle/App.xaml.cs new file mode 100644 index 0000000..99b9ab8 --- /dev/null +++ b/TornadoAce_CJOnStyle/App.xaml.cs @@ -0,0 +1,52 @@ +namespace TornadoAce_CJOnStyle +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + public partial class App : Application + { + private Window? window; + + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + } + + /// + /// Invoked when the application is launched normally by the end user. Other entry points + /// will be used such as when the application is launched to open a specific file. + /// + /// Details about the launch request and process. + protected override void OnLaunched(LaunchActivatedEventArgs e) + { + window ??= new Window + { + Title = "TornadoAce CJ OnStyle CG Automation" + }; + + if (window.Content is not Frame rootFrame) + { + rootFrame = new Frame(); + rootFrame.NavigationFailed += OnNavigationFailed; + window.Content = rootFrame; + } + + _ = rootFrame.Navigate(typeof(MainPage), e.Arguments); + window.Activate(); + } + + /// + /// Invoked when Navigation to a certain page fails + /// + /// The Frame which failed navigation + /// Details about the navigation failure + void OnNavigationFailed(object sender, NavigationFailedEventArgs e) + { + throw new Exception("Failed to load Page " + e.SourcePageType.FullName); + } + } +} diff --git a/TornadoAce_CJOnStyle/Assets/LockScreenLogo.scale-200.png b/TornadoAce_CJOnStyle/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 0000000..7440f0d Binary files /dev/null and b/TornadoAce_CJOnStyle/Assets/LockScreenLogo.scale-200.png differ diff --git a/TornadoAce_CJOnStyle/Assets/SplashScreen.scale-200.png b/TornadoAce_CJOnStyle/Assets/SplashScreen.scale-200.png new file mode 100644 index 0000000..32f486a Binary files /dev/null and b/TornadoAce_CJOnStyle/Assets/SplashScreen.scale-200.png differ diff --git a/TornadoAce_CJOnStyle/Assets/Square150x150Logo.scale-200.png b/TornadoAce_CJOnStyle/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 0000000..53ee377 Binary files /dev/null and b/TornadoAce_CJOnStyle/Assets/Square150x150Logo.scale-200.png differ diff --git a/TornadoAce_CJOnStyle/Assets/Square44x44Logo.scale-200.png b/TornadoAce_CJOnStyle/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 0000000..f713bba Binary files /dev/null and b/TornadoAce_CJOnStyle/Assets/Square44x44Logo.scale-200.png differ diff --git a/TornadoAce_CJOnStyle/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/TornadoAce_CJOnStyle/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 0000000..dc9f5be Binary files /dev/null and b/TornadoAce_CJOnStyle/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/TornadoAce_CJOnStyle/Assets/StoreLogo.png b/TornadoAce_CJOnStyle/Assets/StoreLogo.png new file mode 100644 index 0000000..a4586f2 Binary files /dev/null and b/TornadoAce_CJOnStyle/Assets/StoreLogo.png differ diff --git a/TornadoAce_CJOnStyle/Assets/Wide310x150Logo.scale-200.png b/TornadoAce_CJOnStyle/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 0000000..8b4a5d0 Binary files /dev/null and b/TornadoAce_CJOnStyle/Assets/Wide310x150Logo.scale-200.png differ diff --git a/TornadoAce_CJOnStyle/Domain/CueSheetModels.cs b/TornadoAce_CJOnStyle/Domain/CueSheetModels.cs new file mode 100644 index 0000000..1b7ce3f --- /dev/null +++ b/TornadoAce_CJOnStyle/Domain/CueSheetModels.cs @@ -0,0 +1,11 @@ +namespace TornadoAce_CJOnStyle.Domain +{ + public sealed record CueSheetRecord( + string CueSheetId, + DateTime BroadcastTime, + string ProgramTitle, + string ProductName, + string PriceText, + string CompositionText, + string PromotionText); +} diff --git a/TornadoAce_CJOnStyle/Domain/SceneModels.cs b/TornadoAce_CJOnStyle/Domain/SceneModels.cs new file mode 100644 index 0000000..2bbae93 --- /dev/null +++ b/TornadoAce_CJOnStyle/Domain/SceneModels.cs @@ -0,0 +1,36 @@ +using System.Text.Json; + +namespace TornadoAce_CJOnStyle.Domain +{ + public sealed record SceneTemplateDefinition( + string TemplateId, + string TemplateName, + string TornadoVersion, + IReadOnlyList Mappings); + + public sealed record FieldMappingDefinition( + string CueSheetField, + string SceneObjectName, + string Attribute, + string Rule); + + public sealed record SceneDataSnapshot( + string SceneName, + string TemplateId, + IReadOnlyDictionary VisibleTextObjects, + IReadOnlyList ImagePaths) + { + public string ToPreviewJson() + { + var payload = new + { + scene = SceneName, + templateId = TemplateId, + text = VisibleTextObjects, + images = ImagePaths + }; + + return JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true }); + } + } +} diff --git a/TornadoAce_CJOnStyle/Domain/VerificationModels.cs b/TornadoAce_CJOnStyle/Domain/VerificationModels.cs new file mode 100644 index 0000000..357d7a7 --- /dev/null +++ b/TornadoAce_CJOnStyle/Domain/VerificationModels.cs @@ -0,0 +1,10 @@ +namespace TornadoAce_CJOnStyle.Domain +{ + public sealed record InspectionResult( + string CueSheetId, + string ProgramTitle, + string SceneName, + double MatchRate, + string Severity, + string Decision); +} diff --git a/TornadoAce_CJOnStyle/Imports.cs b/TornadoAce_CJOnStyle/Imports.cs new file mode 100644 index 0000000..02d180e --- /dev/null +++ b/TornadoAce_CJOnStyle/Imports.cs @@ -0,0 +1,10 @@ +global using CommunityToolkit.Mvvm.ComponentModel; +global using CommunityToolkit.Mvvm.Input; +global using CommunityToolkit.Mvvm.Messaging; + +global using TornadoAce_CJOnStyle.ViewModels; +global using TornadoAce_CJOnStyle.Views; + +global using Microsoft.UI.Xaml; +global using Microsoft.UI.Xaml.Controls; +global using Microsoft.UI.Xaml.Navigation; diff --git a/TornadoAce_CJOnStyle/Package.appxmanifest b/TornadoAce_CJOnStyle/Package.appxmanifest new file mode 100644 index 0000000..a2276a6 --- /dev/null +++ b/TornadoAce_CJOnStyle/Package.appxmanifest @@ -0,0 +1,51 @@ + + + + + + + + + + TornadoAce CJ OnStyle + Comtropy + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TornadoAce_CJOnStyle/Properties/PublishProfiles/win-x64.pubxml b/TornadoAce_CJOnStyle/Properties/PublishProfiles/win-x64.pubxml new file mode 100644 index 0000000..eea93de --- /dev/null +++ b/TornadoAce_CJOnStyle/Properties/PublishProfiles/win-x64.pubxml @@ -0,0 +1,19 @@ + + + + + FileSystem + x64 + win-x64 + win10-x64 + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ + true + False + False + True + False + True + + \ No newline at end of file diff --git a/TornadoAce_CJOnStyle/Properties/launchSettings.json b/TornadoAce_CJOnStyle/Properties/launchSettings.json new file mode 100644 index 0000000..933a7f2 --- /dev/null +++ b/TornadoAce_CJOnStyle/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "TornadoAce_CJOnStyle (Package)": { + "commandName": "MsixPackage" + }, + "TornadoAce_CJOnStyle (Unpackaged)": { + "commandName": "Project" + } + } +} diff --git a/TornadoAce_CJOnStyle/Services/AutomationWorkspace.cs b/TornadoAce_CJOnStyle/Services/AutomationWorkspace.cs new file mode 100644 index 0000000..c3b51e8 --- /dev/null +++ b/TornadoAce_CJOnStyle/Services/AutomationWorkspace.cs @@ -0,0 +1,70 @@ +using TornadoAce_CJOnStyle.Domain; + +namespace TornadoAce_CJOnStyle.Services +{ + public sealed class AutomationWorkspace + { + private AutomationWorkspace( + IReadOnlyList cueSheets, + SceneTemplateDefinition activeTemplate, + SceneDataSnapshot activeScene, + IReadOnlyList inspectionResults) + { + CueSheets = cueSheets; + ActiveTemplate = activeTemplate; + ActiveScene = activeScene; + InspectionResults = inspectionResults; + } + + public IReadOnlyList CueSheets { get; } + + public SceneTemplateDefinition ActiveTemplate { get; } + + public SceneDataSnapshot ActiveScene { get; } + + public IReadOnlyList InspectionResults { get; } + + public static AutomationWorkspace CreateDemo() + { + var cueSheets = new[] + { + new CueSheetRecord("CJ-20260817-001", DateTime.Today.AddHours(9), "최화정쇼", "비비안 컴포트 패키지", "79,900원", "브라 4종 + 팬티 4종", "ARS 10% 할인"), + new CueSheetRecord("CJ-20260817-002", DateTime.Today.AddHours(11), "동가게", "프리미엄 한우 세트", "69,900원", "1등급 등심 1.2kg", "무이자 5개월"), + new CueSheetRecord("CJ-20260817-003", DateTime.Today.AddHours(14), "스타일 라이브", "썸머 린넨 셋업", "59,900원", "자켓 + 팬츠", "앱 주문 추가 적립") + }; + + var mappings = new[] + { + new FieldMappingDefinition("programTitle", "TXT_PROGRAM", "Text", "trim"), + new FieldMappingDefinition("productName", "TXT_PRODUCT", "Text", "normalize-space"), + new FieldMappingDefinition("priceText", "TXT_PRICE", "Text", "currency-text"), + new FieldMappingDefinition("compositionText", "TXT_COMPOSITION", "Text", "line-break-safe"), + new FieldMappingDefinition("promotionText", "TXT_PROMOTION", "Text", "special-char-exception") + }; + + var template = new SceneTemplateDefinition("CJ_PRICE_LOWER_01", "가격형 하단 자막 / Tornado Scene", "Tornado2 primary, Tornado3 ready", mappings); + + var scene = new SceneDataSnapshot( + "CJ_PRICE_LOWER_01_0900.tscn", + template.TemplateId, + new Dictionary + { + ["TXT_PROGRAM"] = "최화정쇼", + ["TXT_PRODUCT"] = "비비안 컴포트 패키지", + ["TXT_PRICE"] = "79,900원", + ["TXT_COMPOSITION"] = "브라 4종 + 팬티 4종", + ["TXT_PROMOTION"] = "ARS 10% 할인" + }, + new[] { @"D:\CJOnStyle\Products\VIVIEN\main.png" }); + + var inspections = new[] + { + new InspectionResult("CJ-20260817-001", "최화정쇼", "CJ_PRICE_LOWER_01_0900.tscn", 100, "Low", "1차 통과"), + new InspectionResult("CJ-20260817-002", "동가게", "CJ_PRICE_LOWER_01_1100.tscn", 96.4, "High", "가격 불일치 확인"), + new InspectionResult("CJ-20260817-003", "스타일 라이브", "CJ_PRICE_LOWER_01_1400.tscn", 98.1, "Medium", "특수문자 예외 후보") + }; + + return new AutomationWorkspace(cueSheets, template, scene, inspections); + } + } +} diff --git a/TornadoAce_CJOnStyle/Services/IntegrationInterfaces.cs b/TornadoAce_CJOnStyle/Services/IntegrationInterfaces.cs new file mode 100644 index 0000000..bad40d4 --- /dev/null +++ b/TornadoAce_CJOnStyle/Services/IntegrationInterfaces.cs @@ -0,0 +1,21 @@ +using TornadoAce_CJOnStyle.Domain; + +namespace TornadoAce_CJOnStyle.Services +{ + public interface ICueSheetClient + { + Task> GetCueSheetsAsync(DateOnly broadcastDate, CancellationToken cancellationToken); + } + + public interface ITornadoSceneAdapter + { + Task CreateOrUpdateSceneAsync(CueSheetRecord cueSheet, SceneTemplateDefinition template, CancellationToken cancellationToken); + + Task ExtractSceneDataAsync(string scenePath, CancellationToken cancellationToken); + } + + public interface IInspectionService + { + Task InspectAsync(CueSheetRecord cueSheet, SceneDataSnapshot sceneSnapshot, CancellationToken cancellationToken); + } +} diff --git a/TornadoAce_CJOnStyle/TornadoAce_CJOnStyle.csproj b/TornadoAce_CJOnStyle/TornadoAce_CJOnStyle.csproj new file mode 100644 index 0000000..fd11a15 --- /dev/null +++ b/TornadoAce_CJOnStyle/TornadoAce_CJOnStyle.csproj @@ -0,0 +1,67 @@ + + + + net8.0-windows10.0.19041.0 + 10.0.17763.0 + WinExe + + + true + true + None + + + + enable + latest + enable + TornadoAce_CJOnStyle + + + true + x64 + app.manifest + win-$(Platform).pubxml + win-x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + diff --git a/TornadoAce_CJOnStyle/ViewModels/BaseViewModel.cs b/TornadoAce_CJOnStyle/ViewModels/BaseViewModel.cs new file mode 100644 index 0000000..4216568 --- /dev/null +++ b/TornadoAce_CJOnStyle/ViewModels/BaseViewModel.cs @@ -0,0 +1,13 @@ +namespace TornadoAce_CJOnStyle.ViewModels +{ + public partial class BaseViewModel : ObservableObject + { + private string title = string.Empty; + + public string Title + { + get => title; + set => SetProperty(ref title, value); + } + } +} diff --git a/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs b/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..2cd6f10 --- /dev/null +++ b/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs @@ -0,0 +1,202 @@ +using Microsoft.UI; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using System.Collections.ObjectModel; +using TornadoAce_CJOnStyle.Services; +using Windows.UI; + +namespace TornadoAce_CJOnStyle.ViewModels +{ + public partial class MainViewModel : BaseViewModel + { + private readonly AutomationWorkspace workspace; + + public MainViewModel() + { + Title = "CG 제작 자동화"; + workspace = AutomationWorkspace.CreateDemo(); + + BuildPhase = "INITIAL BUILD"; + OperatorMessage = "큐시트 플랫폼, Tornado Scene 템플릿, 자동 검수 결과를 한 화면에서 확인하는 운영 콘솔 초안입니다."; + LastCueSheetSyncText = "목 데이터 기준 13:25"; + ActiveTemplateName = workspace.ActiveTemplate.TemplateName; + InspectionSummary = "1차 diff 실패 2건 / LLM 검토 대기 1건"; + SceneSnapshotLabel = $"{workspace.ActiveScene.SceneName} / visible text objects"; + SceneSnapshotText = workspace.ActiveScene.ToPreviewJson(); + LlmPolicyText = "사내 LLM 우선, 외부 서비스는 정책 승인 후"; + LlmPolicyDetail = "1차 Text Diffing 실패 건만 2차 LLM 판정으로 이동하고, 가격·구성·방송시간 변경은 높은 중요도로 표시합니다."; + + LoadStatusCards(); + LoadPipelineSteps(); + LoadFieldMappings(); + LoadEndpoints(); + LoadInspectionQueue(); + } + + private string buildPhase = string.Empty; + private string operatorMessage = string.Empty; + private string lastCueSheetSyncText = string.Empty; + private string activeTemplateName = string.Empty; + private string inspectionSummary = string.Empty; + private string sceneSnapshotLabel = string.Empty; + private string sceneSnapshotText = string.Empty; + private string llmPolicyText = string.Empty; + private string llmPolicyDetail = string.Empty; + + public string BuildPhase + { + get => buildPhase; + set => SetProperty(ref buildPhase, value); + } + + public string OperatorMessage + { + get => operatorMessage; + set => SetProperty(ref operatorMessage, value); + } + + public string LastCueSheetSyncText + { + get => lastCueSheetSyncText; + set => SetProperty(ref lastCueSheetSyncText, value); + } + + public string ActiveTemplateName + { + get => activeTemplateName; + set => SetProperty(ref activeTemplateName, value); + } + + public string InspectionSummary + { + get => inspectionSummary; + set => SetProperty(ref inspectionSummary, value); + } + + public string SceneSnapshotLabel + { + get => sceneSnapshotLabel; + set => SetProperty(ref sceneSnapshotLabel, value); + } + + public string SceneSnapshotText + { + get => sceneSnapshotText; + set => SetProperty(ref sceneSnapshotText, value); + } + + public string LlmPolicyText + { + get => llmPolicyText; + set => SetProperty(ref llmPolicyText, value); + } + + public string LlmPolicyDetail + { + get => llmPolicyDetail; + set => SetProperty(ref llmPolicyDetail, value); + } + + public ObservableCollection StatusCards { get; } = []; + + public ObservableCollection PipelineSteps { get; } = []; + + public ObservableCollection FieldMappings { get; } = []; + + public ObservableCollection Endpoints { get; } = []; + + public ObservableCollection InspectionQueue { get; } = []; + + [RelayCommand] + private void RefreshCueSheet() + { + LastCueSheetSyncText = $"목 데이터 동기화 {DateTime.Now:HH:mm:ss}"; + OperatorMessage = "큐시트 목 데이터를 다시 읽었습니다. 실제 API 연결 전까지 이 버튼은 화면 흐름 확인용으로 동작합니다."; + StatusCards[0].Metric = "12"; + StatusCards[0].Detail = "동기화 완료"; + } + + [RelayCommand] + private void GenerateScene() + { + OperatorMessage = "선택된 큐시트 레코드를 Tornado Scene 템플릿에 매핑하는 흐름을 확인했습니다."; + StatusCards[1].Metric = "7"; + StatusCards[1].Detail = "생성 후보"; + } + + [RelayCommand] + private void RunInspection() + { + OperatorMessage = "Scene 데이터와 큐시트 데이터를 비교하는 1차 검수 흐름을 확인했습니다."; + InspectionSummary = "1차 diff 실패 2건 / LLM 검토 대기 1건 / 통과 5건"; + StatusCards[2].Metric = "99.2%"; + StatusCards[2].Detail = "평균 일치율"; + } + + private void LoadStatusCards() + { + StatusCards.Add(new StatusCardViewModel("큐시트 API", "12", "수신 대기", Symbol.Sync, Brush(0xFF45D6D1))); + StatusCards.Add(new StatusCardViewModel("Scene 매핑", "5", "템플릿 정의", Symbol.Map, Brush(0xFFB48CFF))); + StatusCards.Add(new StatusCardViewModel("자동 검수", "98.7%", "목표 99%+", Symbol.Accept, Brush(0xFFFFB547))); + StatusCards.Add(new StatusCardViewModel("Tornado", "Mock", "SDK 어댑터 자리", Symbol.Repair, Brush(0xFF37D67A))); + } + + private void LoadPipelineSteps() + { + PipelineSteps.Add(new PipelineStepViewModel("01", "큐시트 데이터 수신", "AI ON Studio / 자막 큐시트 API", "설계", 35, Brush(0xFF45D6D1))); + PipelineSteps.Add(new PipelineStepViewModel("02", "Data Field Mapping", "큐시트 필드 -> Tornado Scene 객체", "초안", 45, Brush(0xFFB48CFF))); + PipelineSteps.Add(new PipelineStepViewModel("03", "Scene 생성/갱신", "Tornado2 우선, Tornado3 어댑터 준비", "대기", 25, Brush(0xFF37D67A))); + PipelineSteps.Add(new PipelineStepViewModel("04", "Scene Data API 추출", "Text/Image visible object snapshot", "초안", 30, Brush(0xFFFFB547))); + PipelineSteps.Add(new PipelineStepViewModel("05", "자동 검수 피드백", "Text Diffing + LLM 중요도 판정", "초안", 40, Brush(0xFFFF5B61))); + } + + private void LoadFieldMappings() + { + foreach (var mapping in workspace.ActiveTemplate.Mappings) + { + FieldMappings.Add(new FieldMappingViewModel(mapping.CueSheetField, mapping.SceneObjectName, mapping.Rule, mapping.Attribute)); + } + } + + private void LoadEndpoints() + { + Endpoints.Add(new IntegrationEndpointViewModel("큐시트 플랫폼", "GET /api/cuesheets/{broadcastDate}", "AI ON Studio", "Mock", Brush(0xFF45D6D1))); + Endpoints.Add(new IntegrationEndpointViewModel("ERP 편성", "GET /api/schedules/{programCode}", "CJ OnStyle ERP", "대기", Brush(0xFFFFB547))); + Endpoints.Add(new IntegrationEndpointViewModel("Tornado Scene API", "POST /api/scenes/extract", "Tornado Adapter", "설계", Brush(0xFF37D67A))); + Endpoints.Add(new IntegrationEndpointViewModel("LLM 검수", "POST /api/inspection/llm", "Internal LLM", "정책 확인", Brush(0xFFB48CFF))); + } + + private void LoadInspectionQueue() + { + foreach (var item in workspace.InspectionResults) + { + InspectionQueue.Add(new InspectionQueueItemViewModel( + item.ProgramTitle, + item.SceneName, + $"{item.MatchRate:0.0}%", + item.Severity, + item.Decision, + SeverityBrush(item.Severity))); + } + } + + private static SolidColorBrush SeverityBrush(string severity) + { + return severity switch + { + "High" => Brush(0xFFFF5B61), + "Medium" => Brush(0xFFFFB547), + _ => Brush(0xFF37D67A) + }; + } + + private static SolidColorBrush Brush(uint argb) + { + return new SolidColorBrush(Color.FromArgb( + (byte)(argb >> 24), + (byte)(argb >> 16), + (byte)(argb >> 8), + (byte)argb)); + } + } +} diff --git a/TornadoAce_CJOnStyle/ViewModels/ShellModels.cs b/TornadoAce_CJOnStyle/ViewModels/ShellModels.cs new file mode 100644 index 0000000..34199cb --- /dev/null +++ b/TornadoAce_CJOnStyle/ViewModels/ShellModels.cs @@ -0,0 +1,70 @@ +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; + +namespace TornadoAce_CJOnStyle.ViewModels +{ + public sealed partial class StatusCardViewModel : ObservableObject + { + private string metric = string.Empty; + private string detail = string.Empty; + + public StatusCardViewModel(string label, string metric, string detail, Symbol icon, SolidColorBrush accentBrush) + { + Label = label; + Metric = metric; + Detail = detail; + Icon = icon; + AccentBrush = accentBrush; + } + + public string Label { get; } + + public Symbol Icon { get; } + + public SolidColorBrush AccentBrush { get; } + + public string Metric + { + get => metric; + set => SetProperty(ref metric, value); + } + + public string Detail + { + get => detail; + set => SetProperty(ref detail, value); + } + } + + public sealed record PipelineStepViewModel( + string StepNumber, + string Title, + string SystemName, + string Status, + int Completion, + SolidColorBrush AccentBrush); + + public sealed record FieldMappingViewModel( + string CueSheetField, + string SceneObject, + string BindingRule, + string Attribute); + + public sealed record IntegrationEndpointViewModel( + string Name, + string Endpoint, + string Owner, + string Status, + SolidColorBrush StatusBrush) + { + public string OwnerAndStatus => $"{Owner} / {Status}"; + } + + public sealed record InspectionQueueItemViewModel( + string ProgramTitle, + string SceneName, + string MatchRate, + string Severity, + string Decision, + SolidColorBrush SeverityBrush); +} diff --git a/TornadoAce_CJOnStyle/Views/MainPage.xaml b/TornadoAce_CJOnStyle/Views/MainPage.xaml new file mode 100644 index 0000000..4d6ae47 --- /dev/null +++ b/TornadoAce_CJOnStyle/Views/MainPage.xaml @@ -0,0 +1,499 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TornadoAce_CJOnStyle/Views/MainPage.xaml.cs b/TornadoAce_CJOnStyle/Views/MainPage.xaml.cs new file mode 100644 index 0000000..d5de1a4 --- /dev/null +++ b/TornadoAce_CJOnStyle/Views/MainPage.xaml.cs @@ -0,0 +1,17 @@ +namespace TornadoAce_CJOnStyle.Views +{ + /// + /// A simple page that can be used on its own or navigated to within a Frame. + /// + public partial class MainPage : Page + { + private readonly MainViewModel viewModel = new(); + + public MainPage() + { + this.InitializeComponent(); + } + + public MainViewModel ViewModel => viewModel; + } +} diff --git a/TornadoAce_CJOnStyle/app.manifest b/TornadoAce_CJOnStyle/app.manifest new file mode 100644 index 0000000..b6202b6 --- /dev/null +++ b/TornadoAce_CJOnStyle/app.manifest @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + PerMonitorV2 + + + diff --git a/docs/initial-architecture.md b/docs/initial-architecture.md new file mode 100644 index 0000000..4c5677e --- /dev/null +++ b/docs/initial-architecture.md @@ -0,0 +1,24 @@ +# TornadoAce CJ OnStyle 초기 구조 + +## RFP에서 잡은 핵심 흐름 + +1. 큐시트 플랫폼과 ERP 편성 데이터를 API로 수신한다. +2. 큐시트 필드를 사전 정의된 Tornado Scene 템플릿 객체에 매핑한다. +3. Tornado Scene에서 화면에 노출되는 Text/Image 데이터를 추출하는 API를 둔다. +4. 큐시트 데이터와 Scene 데이터를 1차 Text Diffing으로 비교한다. +5. 1차 실패 건은 LLM 검수로 중요도를 판정하고 제작자에게 피드백한다. + +## 현재 초기 빌드 범위 + +- `Domain`: 큐시트, Scene 템플릿, Scene 데이터 스냅샷, 검수 결과 모델. +- `Services`: 실제 API/Tornado SDK 연결을 받을 인터페이스와 목 워크스페이스. +- `ViewModels`: 운영 콘솔에 표시할 상태 카드, 파이프라인, 매핑표, 검수 큐. +- `Views`: WinUI 3 기반 첫 화면. 예제 프로젝트와 같은 데스크톱 앱 방향으로 유지. + +## 다음 설계 결정 지점 + +- Tornado2와 Tornado3의 어댑터 경계를 같은 인터페이스로 유지할지 여부. +- Scene 내부 비노출 객체를 구분할 메타데이터 규칙. +- 큐시트 API 원문 필드명과 화면용 정규화 필드명 분리 방식. +- 특수문자 예외 처리 목록을 룰 파일로 둘지 관리자 UI에서 편집할지 여부. +- LLM이 반환할 중요도 스키마와 감사 로그 보관 범위.