From c25911ca346a3348c9423a84888e5cf5c0806133 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Tue, 9 Jun 2026 14:21:25 +0900 Subject: [PATCH] Load PoC workspace from JSON --- TornadoAce_CJOnStyle/Data/PocWorkspace.json | 108 ++++++++++++++++ .../Services/AutomationWorkspace.cs | 120 +++++++++++++++++- .../TornadoAce_CJOnStyle.csproj | 3 + .../ViewModels/MainViewModel.cs | 2 +- docs/initial-architecture.md | 3 +- docs/poc-requirements.md | 4 +- 6 files changed, 233 insertions(+), 7 deletions(-) create mode 100644 TornadoAce_CJOnStyle/Data/PocWorkspace.json diff --git a/TornadoAce_CJOnStyle/Data/PocWorkspace.json b/TornadoAce_CJOnStyle/Data/PocWorkspace.json new file mode 100644 index 0000000..2c249e8 --- /dev/null +++ b/TornadoAce_CJOnStyle/Data/PocWorkspace.json @@ -0,0 +1,108 @@ +{ + "cueSheets": [ + { + "cueSheetId": "CJ-20260817-001", + "broadcastTime": "2026-08-17T09:00:00", + "programTitle": "최화정쇼", + "productName": "비비안 컴포트 패키지", + "priceText": "79,900원", + "compositionText": "브라 4종 + 팬티 4종", + "promotionText": "ARS 10% 할인" + }, + { + "cueSheetId": "CJ-20260817-002", + "broadcastTime": "2026-08-17T11:00:00", + "programTitle": "동가게", + "productName": "프리미엄 한우 세트", + "priceText": "69,900원", + "compositionText": "1등급 등심 1.2kg", + "promotionText": "무이자 5개월" + }, + { + "cueSheetId": "CJ-20260817-003", + "broadcastTime": "2026-08-17T14:00:00", + "programTitle": "스타일 라이브", + "productName": "썸머 린넨 셋업", + "priceText": "59,900원", + "compositionText": "자켓 + 팬츠", + "promotionText": "앱 주문 추가 적립" + } + ], + "activeTemplate": { + "templateId": "CJ_PRICE_LOWER_01", + "templateName": "가격형 하단 자막 / Tornado Scene", + "tornadoVersion": "Tornado2 primary, Tornado3 ready", + "mappings": [ + { + "cueSheetField": "programTitle", + "sceneObjectName": "TXT_PROGRAM", + "attribute": "Text", + "rule": "trim" + }, + { + "cueSheetField": "productName", + "sceneObjectName": "TXT_PRODUCT", + "attribute": "Text", + "rule": "normalize-space" + }, + { + "cueSheetField": "priceText", + "sceneObjectName": "TXT_PRICE", + "attribute": "Text", + "rule": "currency-text" + }, + { + "cueSheetField": "compositionText", + "sceneObjectName": "TXT_COMPOSITION", + "attribute": "Text", + "rule": "line-break-safe" + }, + { + "cueSheetField": "promotionText", + "sceneObjectName": "TXT_PROMOTION", + "attribute": "Text", + "rule": "special-char-exception" + } + ] + }, + "activeScene": { + "sceneName": "CJ_PRICE_LOWER_01_0900.tscn", + "templateId": "CJ_PRICE_LOWER_01", + "visibleTextObjects": { + "TXT_PROGRAM": "최화정쇼", + "TXT_PRODUCT": "비비안 컴포트 패키지", + "TXT_PRICE": "79,900원", + "TXT_COMPOSITION": "브라 4종 + 팬티 4종", + "TXT_PROMOTION": "ARS 10% 할인" + }, + "imagePaths": [ + "D:\\CJOnStyle\\Products\\VIVIEN\\main.png" + ] + }, + "inspectionResults": [ + { + "cueSheetId": "CJ-20260817-001", + "programTitle": "최화정쇼", + "sceneName": "CJ_PRICE_LOWER_01_0900.tscn", + "matchRate": 100, + "severity": "Low", + "decision": "1차 통과" + }, + { + "cueSheetId": "CJ-20260817-002", + "programTitle": "동가게", + "sceneName": "CJ_PRICE_LOWER_01_1100.tscn", + "matchRate": 96.4, + "severity": "High", + "decision": "가격 불일치 확인" + }, + { + "cueSheetId": "CJ-20260817-003", + "programTitle": "스타일 라이브", + "sceneName": "CJ_PRICE_LOWER_01_1400.tscn", + "matchRate": 98.1, + "severity": "Medium", + "decision": "특수문자 예외 후보" + } + ] +} diff --git a/TornadoAce_CJOnStyle/Services/AutomationWorkspace.cs b/TornadoAce_CJOnStyle/Services/AutomationWorkspace.cs index c3b51e8..07b0a1b 100644 --- a/TornadoAce_CJOnStyle/Services/AutomationWorkspace.cs +++ b/TornadoAce_CJOnStyle/Services/AutomationWorkspace.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using TornadoAce_CJOnStyle.Domain; namespace TornadoAce_CJOnStyle.Services @@ -25,12 +26,50 @@ namespace TornadoAce_CJOnStyle.Services public IReadOnlyList InspectionResults { get; } public static AutomationWorkspace CreateDemo() + { + return TryCreateFromSeedFile() ?? CreateEmbeddedDemo(); + } + + private static AutomationWorkspace? TryCreateFromSeedFile() + { + var seedPath = Path.Combine(AppContext.BaseDirectory, "Data", "PocWorkspace.json"); + if (!File.Exists(seedPath)) + { + return null; + } + + var json = File.ReadAllText(seedPath); + var seed = JsonSerializer.Deserialize( + json, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + if (seed?.CueSheets is not { Count: > 0 } cueSheets + || seed.ActiveTemplate is null + || seed.ActiveScene is null) + { + return null; + } + + var template = seed.ActiveTemplate.ToDomain(); + var scene = seed.ActiveScene.ToDomain(); + var inspections = seed.InspectionResults + .Select(inspection => inspection.ToDomain()) + .ToArray(); + + return new AutomationWorkspace( + cueSheets.Select(cueSheet => cueSheet.ToDomain()).ToArray(), + template, + scene, + inspections); + } + + private static AutomationWorkspace CreateEmbeddedDemo() { 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원", "자켓 + 팬츠", "앱 주문 추가 적립") + new CueSheetRecord("CJ-20260817-001", new DateTime(2026, 8, 17, 9, 0, 0), "최화정쇼", "비비안 컴포트 패키지", "79,900원", "브라 4종 + 팬티 4종", "ARS 10% 할인"), + new CueSheetRecord("CJ-20260817-002", new DateTime(2026, 8, 17, 11, 0, 0), "동가게", "프리미엄 한우 세트", "69,900원", "1등급 등심 1.2kg", "무이자 5개월"), + new CueSheetRecord("CJ-20260817-003", new DateTime(2026, 8, 17, 14, 0, 0), "스타일 라이브", "썸머 린넨 셋업", "59,900원", "자켓 + 팬츠", "앱 주문 추가 적립") }; var mappings = new[] @@ -66,5 +105,80 @@ namespace TornadoAce_CJOnStyle.Services return new AutomationWorkspace(cueSheets, template, scene, inspections); } + + private sealed record WorkspaceSeed( + List CueSheets, + SceneTemplateSeed ActiveTemplate, + SceneDataSeed ActiveScene, + List InspectionResults); + + private sealed record CueSheetSeed( + string CueSheetId, + DateTime BroadcastTime, + string ProgramTitle, + string ProductName, + string PriceText, + string CompositionText, + string PromotionText) + { + public CueSheetRecord ToDomain() + { + return new CueSheetRecord(CueSheetId, BroadcastTime, ProgramTitle, ProductName, PriceText, CompositionText, PromotionText); + } + } + + private sealed record SceneTemplateSeed( + string TemplateId, + string TemplateName, + string TornadoVersion, + List Mappings) + { + public SceneTemplateDefinition ToDomain() + { + return new SceneTemplateDefinition( + TemplateId, + TemplateName, + TornadoVersion, + Mappings.Select(mapping => mapping.ToDomain()).ToArray()); + } + } + + private sealed record FieldMappingSeed( + string CueSheetField, + string SceneObjectName, + string Attribute, + string Rule) + { + public FieldMappingDefinition ToDomain() + { + return new FieldMappingDefinition(CueSheetField, SceneObjectName, Attribute, Rule); + } + } + + private sealed record SceneDataSeed( + string SceneName, + string TemplateId, + Dictionary VisibleTextObjects, + List ImagePaths) + { + public SceneDataSnapshot ToDomain() + { + return new SceneDataSnapshot(SceneName, TemplateId, VisibleTextObjects, ImagePaths); + } + } + + private sealed record InspectionSeed( + string CueSheetId, + string ProgramTitle, + string SceneName, + double MatchRate, + string Severity, + string Decision) + { + public InspectionResult ToDomain() + { + return new InspectionResult(CueSheetId, ProgramTitle, SceneName, MatchRate, Severity, Decision); + } + } } } diff --git a/TornadoAce_CJOnStyle/TornadoAce_CJOnStyle.csproj b/TornadoAce_CJOnStyle/TornadoAce_CJOnStyle.csproj index d198ba4..42a3702 100644 --- a/TornadoAce_CJOnStyle/TornadoAce_CJOnStyle.csproj +++ b/TornadoAce_CJOnStyle/TornadoAce_CJOnStyle.csproj @@ -45,6 +45,9 @@ + + PreserveNewest + diff --git a/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs b/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs index e427f88..4538961 100644 --- a/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs +++ b/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs @@ -250,7 +250,7 @@ namespace TornadoAce_CJOnStyle.ViewModels private void LoadRequirementCoverage() { - RequirementCoverage.Add(new PocRequirementViewModel("RFP-01", "큐시트 API 연동", "PoC", "샘플 큐시트 선택", Brush(0xFF45D6D1))); + RequirementCoverage.Add(new PocRequirementViewModel("RFP-01", "큐시트 API 연동", "PoC", "JSON Mock 수신", Brush(0xFF45D6D1))); RequirementCoverage.Add(new PocRequirementViewModel("RFP-02", "ERP 편성 연동", "대기", "엔드포인트 자리", Brush(0xFFFFB547))); 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))); diff --git a/docs/initial-architecture.md b/docs/initial-architecture.md index 04ccbf8..d18b09e 100644 --- a/docs/initial-architecture.md +++ b/docs/initial-architecture.md @@ -11,7 +11,8 @@ ## 현재 초기 빌드 범위 - `Domain`: 큐시트, Scene 템플릿, Scene 데이터 스냅샷, 검수 결과 모델. -- `Services`: 실제 API/Tornado SDK 연결을 받을 인터페이스와 목 워크스페이스. +- `Data`: 큐시트, Scene 템플릿, Scene 추출값을 담은 JSON 기반 PoC 샘플. +- `Services`: 실제 API/Tornado SDK 연결을 받을 인터페이스, JSON 로더, 목 워크스페이스. - `ViewModels`: 운영 콘솔에 표시할 상태 카드, 파이프라인, 매핑표, 검수 큐. - `Views`: WinUI 3 기반 첫 화면. 예제 프로젝트와 같은 데스크톱 앱 방향으로 유지. diff --git a/docs/poc-requirements.md b/docs/poc-requirements.md index 1b0c041..1baaef0 100644 --- a/docs/poc-requirements.md +++ b/docs/poc-requirements.md @@ -13,7 +13,7 @@ | ID | RFP 요구사항 | 초기 PoC 반영 상태 | 현재 구현 근거 | | --- | --- | --- | --- | -| RFP-01 | 신규 큐시트 플랫폼 API 연동 | Mock 데이터 수신 흐름 구현 | `AutomationWorkspace.CueSheets`, `RefreshCueSheetCommand` | +| RFP-01 | 신규 큐시트 플랫폼 API 연동 | JSON Mock 데이터 수신 흐름 구현 | `Data/PocWorkspace.json`, `AutomationWorkspace.CueSheets`, `RefreshCueSheetCommand` | | RFP-02 | ERP 편성데이터 연동 | 연동 엔드포인트 자리 표시 | `IntegrationEndpointViewModel` | | RFP-03 | CG 포맷 제작 자동화, Data Field Mapping | 큐시트 필드와 Scene 객체 매핑 구현 | `SceneTemplateDefinition`, `PocAutomationEngine.GenerateScene` | | RFP-04 | Tornado Scene API | Scene visible Text/Image 스냅샷 샘플 구현 | `SceneDataSnapshot.ToPreviewJson` | @@ -32,7 +32,7 @@ ## 다음 구현 후보 -- 큐시트/ERP Mock 데이터를 JSON 파일로 분리하여 실제 API 응답 구조와 맞춘다. +- ERP Mock 데이터를 JSON 파일로 분리하여 실제 편성 API 응답 구조와 맞춘다. - Tornado2/Tornado3 어댑터를 동일 인터페이스로 유지하고, 버전별 구현체를 분리한다. - Scene 내부 비노출 객체를 구분할 메타데이터 규칙을 정의한다. - 1차 검수 실패 사유를 필드 단위로 누적하고 LLM 요청 후보로 전달한다.