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이 반환할 중요도 스키마와 감사 로그 보관 범위.