Add ERP schedule matching PoC
This commit is contained in:
@@ -28,6 +28,35 @@
|
|||||||
"promotionText": "앱 주문 추가 적립"
|
"promotionText": "앱 주문 추가 적립"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"erpSchedules": [
|
||||||
|
{
|
||||||
|
"programCode": "PGM-CHOI-0900",
|
||||||
|
"programTitle": "최화정쇼",
|
||||||
|
"broadcastTime": "2026-08-17T09:00:00",
|
||||||
|
"durationMinutes": 60,
|
||||||
|
"channelName": "CJ OnStyle Live",
|
||||||
|
"merchandiserName": "MD 이수진",
|
||||||
|
"productCategory": "패션 이너웨어"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"programCode": "PGM-DONG-1100",
|
||||||
|
"programTitle": "동가게",
|
||||||
|
"broadcastTime": "2026-08-17T11:00:00",
|
||||||
|
"durationMinutes": 50,
|
||||||
|
"channelName": "CJ OnStyle Live",
|
||||||
|
"merchandiserName": "MD 박정우",
|
||||||
|
"productCategory": "신선식품"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"programCode": "PGM-STYLE-1400",
|
||||||
|
"programTitle": "스타일 라이브",
|
||||||
|
"broadcastTime": "2026-08-17T14:00:00",
|
||||||
|
"durationMinutes": 70,
|
||||||
|
"channelName": "CJ OnStyle Live",
|
||||||
|
"merchandiserName": "MD 김하린",
|
||||||
|
"productCategory": "패션 의류"
|
||||||
|
}
|
||||||
|
],
|
||||||
"activeTemplate": {
|
"activeTemplate": {
|
||||||
"templateId": "CJ_PRICE_LOWER_01",
|
"templateId": "CJ_PRICE_LOWER_01",
|
||||||
"templateName": "가격형 하단 자막 / Tornado Scene",
|
"templateName": "가격형 하단 자막 / Tornado Scene",
|
||||||
|
|||||||
11
TornadoAce_CJOnStyle/Domain/ScheduleModels.cs
Normal file
11
TornadoAce_CJOnStyle/Domain/ScheduleModels.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace TornadoAce_CJOnStyle.Domain
|
||||||
|
{
|
||||||
|
public sealed record ErpScheduleRecord(
|
||||||
|
string ProgramCode,
|
||||||
|
string ProgramTitle,
|
||||||
|
DateTime BroadcastTime,
|
||||||
|
int DurationMinutes,
|
||||||
|
string ChannelName,
|
||||||
|
string MerchandiserName,
|
||||||
|
string ProductCategory);
|
||||||
|
}
|
||||||
@@ -7,11 +7,13 @@ namespace TornadoAce_CJOnStyle.Services
|
|||||||
{
|
{
|
||||||
private AutomationWorkspace(
|
private AutomationWorkspace(
|
||||||
IReadOnlyList<CueSheetRecord> cueSheets,
|
IReadOnlyList<CueSheetRecord> cueSheets,
|
||||||
|
IReadOnlyList<ErpScheduleRecord> erpSchedules,
|
||||||
SceneTemplateDefinition activeTemplate,
|
SceneTemplateDefinition activeTemplate,
|
||||||
SceneDataSnapshot activeScene,
|
SceneDataSnapshot activeScene,
|
||||||
IReadOnlyList<InspectionResult> inspectionResults)
|
IReadOnlyList<InspectionResult> inspectionResults)
|
||||||
{
|
{
|
||||||
CueSheets = cueSheets;
|
CueSheets = cueSheets;
|
||||||
|
ErpSchedules = erpSchedules;
|
||||||
ActiveTemplate = activeTemplate;
|
ActiveTemplate = activeTemplate;
|
||||||
ActiveScene = activeScene;
|
ActiveScene = activeScene;
|
||||||
InspectionResults = inspectionResults;
|
InspectionResults = inspectionResults;
|
||||||
@@ -19,6 +21,8 @@ namespace TornadoAce_CJOnStyle.Services
|
|||||||
|
|
||||||
public IReadOnlyList<CueSheetRecord> CueSheets { get; }
|
public IReadOnlyList<CueSheetRecord> CueSheets { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<ErpScheduleRecord> ErpSchedules { get; }
|
||||||
|
|
||||||
public SceneTemplateDefinition ActiveTemplate { get; }
|
public SceneTemplateDefinition ActiveTemplate { get; }
|
||||||
|
|
||||||
public SceneDataSnapshot ActiveScene { get; }
|
public SceneDataSnapshot ActiveScene { get; }
|
||||||
@@ -52,12 +56,16 @@ namespace TornadoAce_CJOnStyle.Services
|
|||||||
|
|
||||||
var template = seed.ActiveTemplate.ToDomain();
|
var template = seed.ActiveTemplate.ToDomain();
|
||||||
var scene = seed.ActiveScene.ToDomain();
|
var scene = seed.ActiveScene.ToDomain();
|
||||||
|
var schedules = seed.ErpSchedules?
|
||||||
|
.Select(schedule => schedule.ToDomain())
|
||||||
|
.ToArray() ?? [];
|
||||||
var inspections = seed.InspectionResults
|
var inspections = seed.InspectionResults
|
||||||
.Select(inspection => inspection.ToDomain())
|
.Select(inspection => inspection.ToDomain())
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
return new AutomationWorkspace(
|
return new AutomationWorkspace(
|
||||||
cueSheets.Select(cueSheet => cueSheet.ToDomain()).ToArray(),
|
cueSheets.Select(cueSheet => cueSheet.ToDomain()).ToArray(),
|
||||||
|
schedules,
|
||||||
template,
|
template,
|
||||||
scene,
|
scene,
|
||||||
inspections);
|
inspections);
|
||||||
@@ -72,6 +80,13 @@ namespace TornadoAce_CJOnStyle.Services
|
|||||||
new CueSheetRecord("CJ-20260817-003", new DateTime(2026, 8, 17, 14, 0, 0), "스타일 라이브", "썸머 린넨 셋업", "59,900원", "자켓 + 팬츠", "앱 주문 추가 적립")
|
new CueSheetRecord("CJ-20260817-003", new DateTime(2026, 8, 17, 14, 0, 0), "스타일 라이브", "썸머 린넨 셋업", "59,900원", "자켓 + 팬츠", "앱 주문 추가 적립")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var schedules = new[]
|
||||||
|
{
|
||||||
|
new ErpScheduleRecord("PGM-CHOI-0900", "최화정쇼", new DateTime(2026, 8, 17, 9, 0, 0), 60, "CJ OnStyle Live", "MD 이수진", "패션 이너웨어"),
|
||||||
|
new ErpScheduleRecord("PGM-DONG-1100", "동가게", new DateTime(2026, 8, 17, 11, 0, 0), 50, "CJ OnStyle Live", "MD 박정우", "신선식품"),
|
||||||
|
new ErpScheduleRecord("PGM-STYLE-1400", "스타일 라이브", new DateTime(2026, 8, 17, 14, 0, 0), 70, "CJ OnStyle Live", "MD 김하린", "패션 의류")
|
||||||
|
};
|
||||||
|
|
||||||
var mappings = new[]
|
var mappings = new[]
|
||||||
{
|
{
|
||||||
new FieldMappingDefinition("programTitle", "TXT_PROGRAM", "Text", "trim"),
|
new FieldMappingDefinition("programTitle", "TXT_PROGRAM", "Text", "trim"),
|
||||||
@@ -103,11 +118,12 @@ namespace TornadoAce_CJOnStyle.Services
|
|||||||
new InspectionResult("CJ-20260817-003", "스타일 라이브", "CJ_PRICE_LOWER_01_1400.tscn", 98.1, "Medium", "특수문자 예외 후보")
|
new InspectionResult("CJ-20260817-003", "스타일 라이브", "CJ_PRICE_LOWER_01_1400.tscn", 98.1, "Medium", "특수문자 예외 후보")
|
||||||
};
|
};
|
||||||
|
|
||||||
return new AutomationWorkspace(cueSheets, template, scene, inspections);
|
return new AutomationWorkspace(cueSheets, schedules, template, scene, inspections);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed record WorkspaceSeed(
|
private sealed record WorkspaceSeed(
|
||||||
List<CueSheetSeed> CueSheets,
|
List<CueSheetSeed> CueSheets,
|
||||||
|
List<ErpScheduleSeed>? ErpSchedules,
|
||||||
SceneTemplateSeed ActiveTemplate,
|
SceneTemplateSeed ActiveTemplate,
|
||||||
SceneDataSeed ActiveScene,
|
SceneDataSeed ActiveScene,
|
||||||
List<InspectionSeed> InspectionResults);
|
List<InspectionSeed> InspectionResults);
|
||||||
@@ -127,6 +143,28 @@ namespace TornadoAce_CJOnStyle.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed record ErpScheduleSeed(
|
||||||
|
string ProgramCode,
|
||||||
|
string ProgramTitle,
|
||||||
|
DateTime BroadcastTime,
|
||||||
|
int DurationMinutes,
|
||||||
|
string ChannelName,
|
||||||
|
string MerchandiserName,
|
||||||
|
string ProductCategory)
|
||||||
|
{
|
||||||
|
public ErpScheduleRecord ToDomain()
|
||||||
|
{
|
||||||
|
return new ErpScheduleRecord(
|
||||||
|
ProgramCode,
|
||||||
|
ProgramTitle,
|
||||||
|
BroadcastTime,
|
||||||
|
DurationMinutes,
|
||||||
|
ChannelName,
|
||||||
|
MerchandiserName,
|
||||||
|
ProductCategory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private sealed record SceneTemplateSeed(
|
private sealed record SceneTemplateSeed(
|
||||||
string TemplateId,
|
string TemplateId,
|
||||||
string TemplateName,
|
string TemplateName,
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ namespace TornadoAce_CJOnStyle.Services
|
|||||||
Task<IReadOnlyList<CueSheetRecord>> GetCueSheetsAsync(DateOnly broadcastDate, CancellationToken cancellationToken);
|
Task<IReadOnlyList<CueSheetRecord>> GetCueSheetsAsync(DateOnly broadcastDate, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface IErpScheduleClient
|
||||||
|
{
|
||||||
|
Task<IReadOnlyList<ErpScheduleRecord>> GetSchedulesAsync(DateOnly broadcastDate, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
public interface ITornadoSceneAdapter
|
public interface ITornadoSceneAdapter
|
||||||
{
|
{
|
||||||
Task<SceneDataSnapshot> CreateOrUpdateSceneAsync(CueSheetRecord cueSheet, SceneTemplateDefinition template, CancellationToken cancellationToken);
|
Task<SceneDataSnapshot> CreateOrUpdateSceneAsync(CueSheetRecord cueSheet, SceneTemplateDefinition template, CancellationToken cancellationToken);
|
||||||
|
|||||||
@@ -28,6 +28,15 @@ namespace TornadoAce_CJOnStyle.Services
|
|||||||
return BuildSnapshot(cueSheet, template, BuildVisibleTextObjects(cueSheet, template));
|
return BuildSnapshot(cueSheet, template, BuildVisibleTextObjects(cueSheet, template));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ErpScheduleRecord? FindSchedule(CueSheetRecord cueSheet)
|
||||||
|
{
|
||||||
|
return workspace.ErpSchedules
|
||||||
|
.OrderBy(schedule => Math.Abs((schedule.BroadcastTime - cueSheet.BroadcastTime).TotalMinutes))
|
||||||
|
.FirstOrDefault(schedule =>
|
||||||
|
string.Equals(schedule.ProgramTitle, cueSheet.ProgramTitle, StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& Math.Abs((schedule.BroadcastTime - cueSheet.BroadcastTime).TotalMinutes) <= 15);
|
||||||
|
}
|
||||||
|
|
||||||
public PocInspectionSummary InspectWorkspace(SceneTemplateDefinition template)
|
public PocInspectionSummary InspectWorkspace(SceneTemplateDefinition template)
|
||||||
{
|
{
|
||||||
var results = workspace.CueSheets
|
var results = workspace.CueSheets
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace TornadoAce_CJOnStyle.ViewModels
|
|||||||
private readonly PocAutomationEngine automationEngine;
|
private readonly PocAutomationEngine automationEngine;
|
||||||
private int selectedCueSheetIndex;
|
private int selectedCueSheetIndex;
|
||||||
private CueSheetRecord activeCueSheet = null!;
|
private CueSheetRecord activeCueSheet = null!;
|
||||||
|
private ErpScheduleRecord? activeSchedule;
|
||||||
private SceneDataSnapshot activeScene = null!;
|
private SceneDataSnapshot activeScene = null!;
|
||||||
|
|
||||||
public MainViewModel()
|
public MainViewModel()
|
||||||
@@ -25,6 +26,7 @@ namespace TornadoAce_CJOnStyle.ViewModels
|
|||||||
var initialSelection = automationEngine.SelectCueSheet(selectedCueSheetIndex);
|
var initialSelection = automationEngine.SelectCueSheet(selectedCueSheetIndex);
|
||||||
var initialInspectionSummary = automationEngine.InspectWorkspace(workspace.ActiveTemplate);
|
var initialInspectionSummary = automationEngine.InspectWorkspace(workspace.ActiveTemplate);
|
||||||
activeCueSheet = initialSelection.Record;
|
activeCueSheet = initialSelection.Record;
|
||||||
|
activeSchedule = automationEngine.FindSchedule(activeCueSheet);
|
||||||
activeScene = automationEngine.GenerateScene(activeCueSheet, workspace.ActiveTemplate);
|
activeScene = automationEngine.GenerateScene(activeCueSheet, workspace.ActiveTemplate);
|
||||||
|
|
||||||
BuildPhase = "INITIAL BUILD";
|
BuildPhase = "INITIAL BUILD";
|
||||||
@@ -44,6 +46,7 @@ namespace TornadoAce_CJOnStyle.ViewModels
|
|||||||
LoadFieldMappings();
|
LoadFieldMappings();
|
||||||
LoadEndpoints();
|
LoadEndpoints();
|
||||||
LoadRequirementCoverage();
|
LoadRequirementCoverage();
|
||||||
|
LoadScheduleMatches();
|
||||||
LoadInspectionQueue(initialInspectionSummary.Results);
|
LoadInspectionQueue(initialInspectionSummary.Results);
|
||||||
LoadInspectionDetails(initialInspectionSummary.Results);
|
LoadInspectionDetails(initialInspectionSummary.Results);
|
||||||
}
|
}
|
||||||
@@ -127,6 +130,8 @@ namespace TornadoAce_CJOnStyle.ViewModels
|
|||||||
|
|
||||||
public ObservableCollection<PocRequirementViewModel> RequirementCoverage { get; } = [];
|
public ObservableCollection<PocRequirementViewModel> RequirementCoverage { get; } = [];
|
||||||
|
|
||||||
|
public ObservableCollection<ScheduleMatchViewModel> ScheduleMatches { get; } = [];
|
||||||
|
|
||||||
public ObservableCollection<InspectionQueueItemViewModel> InspectionQueue { get; } = [];
|
public ObservableCollection<InspectionQueueItemViewModel> InspectionQueue { get; } = [];
|
||||||
|
|
||||||
public ObservableCollection<InspectionDetailItemViewModel> InspectionDetails { get; } = [];
|
public ObservableCollection<InspectionDetailItemViewModel> InspectionDetails { get; } = [];
|
||||||
@@ -182,10 +187,11 @@ namespace TornadoAce_CJOnStyle.ViewModels
|
|||||||
selectedCueSheetIndex++;
|
selectedCueSheetIndex++;
|
||||||
var selection = automationEngine.SelectCueSheet(selectedCueSheetIndex);
|
var selection = automationEngine.SelectCueSheet(selectedCueSheetIndex);
|
||||||
activeCueSheet = selection.Record;
|
activeCueSheet = selection.Record;
|
||||||
|
activeSchedule = automationEngine.FindSchedule(activeCueSheet);
|
||||||
activeScene = automationEngine.GenerateScene(activeCueSheet, workspace.ActiveTemplate);
|
activeScene = automationEngine.GenerateScene(activeCueSheet, workspace.ActiveTemplate);
|
||||||
|
|
||||||
LastCueSheetSyncText = FormatCueSheetSelection(selection);
|
LastCueSheetSyncText = FormatCueSheetSelection(selection);
|
||||||
OperatorMessage = $"{activeCueSheet.ProgramTitle} / {activeCueSheet.ProductName} 큐시트를 선택했습니다.";
|
OperatorMessage = $"{activeCueSheet.ProgramTitle} / {activeCueSheet.ProductName} 큐시트를 선택했습니다. {FormatScheduleMatch(activeSchedule)}";
|
||||||
StatusCards[0].Metric = selection.TotalCount.ToString();
|
StatusCards[0].Metric = selection.TotalCount.ToString();
|
||||||
StatusCards[0].Detail = $"선택 {selection.Position}/{selection.TotalCount}";
|
StatusCards[0].Detail = $"선택 {selection.Position}/{selection.TotalCount}";
|
||||||
ApplySceneSnapshot(activeScene);
|
ApplySceneSnapshot(activeScene);
|
||||||
@@ -225,7 +231,7 @@ namespace TornadoAce_CJOnStyle.ViewModels
|
|||||||
|
|
||||||
private void LoadPipelineSteps()
|
private void LoadPipelineSteps()
|
||||||
{
|
{
|
||||||
PipelineSteps.Add(new PipelineStepViewModel("01", "큐시트 데이터 수신", "AI ON Studio / 자막 큐시트 API", "설계", 35, Brush(0xFF45D6D1)));
|
PipelineSteps.Add(new PipelineStepViewModel("01", "큐시트/ERP 데이터 수신", "AI ON Studio / CJ OnStyle ERP", "PoC", 55, Brush(0xFF45D6D1)));
|
||||||
PipelineSteps.Add(new PipelineStepViewModel("02", "Data Field Mapping", "큐시트 필드 -> Tornado Scene 객체", "초안", 45, Brush(0xFFB48CFF)));
|
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("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("04", "Scene Data API 추출", "Text/Image visible object snapshot", "초안", 30, Brush(0xFFFFB547)));
|
||||||
@@ -243,7 +249,7 @@ namespace TornadoAce_CJOnStyle.ViewModels
|
|||||||
private void LoadEndpoints()
|
private void LoadEndpoints()
|
||||||
{
|
{
|
||||||
Endpoints.Add(new IntegrationEndpointViewModel("큐시트 플랫폼", "GET /api/cuesheets/{broadcastDate}", "AI ON Studio", "Mock", Brush(0xFF45D6D1)));
|
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("ERP 편성", "GET /api/schedules/{programCode}", "CJ OnStyle ERP", "Mock", Brush(0xFF37D67A)));
|
||||||
Endpoints.Add(new IntegrationEndpointViewModel("Tornado Scene API", "POST /api/scenes/extract", "Tornado Adapter", "설계", Brush(0xFF37D67A)));
|
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)));
|
Endpoints.Add(new IntegrationEndpointViewModel("LLM 검수", "POST /api/inspection/llm", "Internal LLM", "정책 확인", Brush(0xFFB48CFF)));
|
||||||
}
|
}
|
||||||
@@ -251,13 +257,32 @@ namespace TornadoAce_CJOnStyle.ViewModels
|
|||||||
private void LoadRequirementCoverage()
|
private void LoadRequirementCoverage()
|
||||||
{
|
{
|
||||||
RequirementCoverage.Add(new PocRequirementViewModel("RFP-01", "큐시트 API 연동", "PoC", "JSON Mock 수신", 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-02", "ERP 편성 연동", "PoC", "편성 JSON 매칭", Brush(0xFF37D67A)));
|
||||||
RequirementCoverage.Add(new PocRequirementViewModel("RFP-03", "Data Field Mapping", "PoC", "Scene 스냅샷 생성", 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-04", "Tornado Scene API", "PoC", "Text/Image 추출 샘플", Brush(0xFF37D67A)));
|
||||||
RequirementCoverage.Add(new PocRequirementViewModel("RFP-05", "관리자 매핑 기능", "초안", "매핑표 UI", Brush(0xFFB48CFF)));
|
RequirementCoverage.Add(new PocRequirementViewModel("RFP-05", "관리자 매핑 기능", "초안", "매핑표 UI", Brush(0xFFB48CFF)));
|
||||||
RequirementCoverage.Add(new PocRequirementViewModel("RFP-06", "1·2단계 자동 검수", "PoC", "필드 상세 Diff + LLM 후보", Brush(0xFF45D6D1)));
|
RequirementCoverage.Add(new PocRequirementViewModel("RFP-06", "1·2단계 자동 검수", "PoC", "필드 상세 Diff + LLM 후보", Brush(0xFF45D6D1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LoadScheduleMatches()
|
||||||
|
{
|
||||||
|
ScheduleMatches.Clear();
|
||||||
|
|
||||||
|
foreach (var cueSheet in workspace.CueSheets)
|
||||||
|
{
|
||||||
|
var schedule = automationEngine.FindSchedule(cueSheet);
|
||||||
|
var isMatched = schedule is not null;
|
||||||
|
|
||||||
|
ScheduleMatches.Add(new ScheduleMatchViewModel(
|
||||||
|
cueSheet.ProgramTitle,
|
||||||
|
$"{cueSheet.BroadcastTime:HH:mm} / {cueSheet.ProductName}",
|
||||||
|
schedule?.ProgramCode ?? "-",
|
||||||
|
schedule is null ? "편성 미확인" : $"{schedule.MerchandiserName} / {schedule.ProductCategory}",
|
||||||
|
isMatched ? "매칭" : "확인",
|
||||||
|
isMatched ? Brush(0xFF37D67A) : Brush(0xFFFFB547)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void LoadInspectionQueue(IEnumerable<InspectionResult> results)
|
private void LoadInspectionQueue(IEnumerable<InspectionResult> results)
|
||||||
{
|
{
|
||||||
InspectionQueue.Clear();
|
InspectionQueue.Clear();
|
||||||
@@ -315,6 +340,13 @@ namespace TornadoAce_CJOnStyle.ViewModels
|
|||||||
return $"통과 {summary.PassedCount}건 / 확인 {summary.ReviewCount}건 / 평균 {summary.AverageMatchRate:0.0}%";
|
return $"통과 {summary.PassedCount}건 / 확인 {summary.ReviewCount}건 / 평균 {summary.AverageMatchRate:0.0}%";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string FormatScheduleMatch(ErpScheduleRecord? schedule)
|
||||||
|
{
|
||||||
|
return schedule is null
|
||||||
|
? "ERP 편성은 확인 대기입니다."
|
||||||
|
: $"ERP 편성 {schedule.ProgramCode}와 매칭됐습니다.";
|
||||||
|
}
|
||||||
|
|
||||||
private static SolidColorBrush SeverityBrush(string severity)
|
private static SolidColorBrush SeverityBrush(string severity)
|
||||||
{
|
{
|
||||||
return severity switch
|
return severity switch
|
||||||
|
|||||||
@@ -67,6 +67,14 @@ namespace TornadoAce_CJOnStyle.ViewModels
|
|||||||
string Evidence,
|
string Evidence,
|
||||||
SolidColorBrush StatusBrush);
|
SolidColorBrush StatusBrush);
|
||||||
|
|
||||||
|
public sealed record ScheduleMatchViewModel(
|
||||||
|
string ProgramTitle,
|
||||||
|
string BroadcastSlot,
|
||||||
|
string ProgramCode,
|
||||||
|
string OwnerAndCategory,
|
||||||
|
string Status,
|
||||||
|
SolidColorBrush StatusBrush);
|
||||||
|
|
||||||
public sealed record InspectionQueueItemViewModel(
|
public sealed record InspectionQueueItemViewModel(
|
||||||
string ProgramTitle,
|
string ProgramTitle,
|
||||||
string SceneName,
|
string SceneName,
|
||||||
|
|||||||
@@ -385,6 +385,57 @@
|
|||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
|
|
||||||
|
<Border
|
||||||
|
Height="1"
|
||||||
|
Background="{StaticResource PanelStrokeBrush}" />
|
||||||
|
|
||||||
|
<TextBlock Style="{StaticResource SectionTitleTextStyle}" Text="ERP 편성 매칭" />
|
||||||
|
<ItemsControl ItemsSource="{x:Bind ViewModel.ScheduleMatches, Mode=OneWay}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:ScheduleMatchViewModel">
|
||||||
|
<Grid
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
ColumnSpacing="10">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackPanel Spacing="2">
|
||||||
|
<TextBlock
|
||||||
|
FontFamily="Bahnschrift SemiBold"
|
||||||
|
Foreground="{StaticResource TextPrimaryBrush}"
|
||||||
|
Text="{x:Bind ProgramTitle}"
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
<TextBlock
|
||||||
|
Style="{StaticResource MonoTextStyle}"
|
||||||
|
Text="{x:Bind ProgramCode}"
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
<TextBlock
|
||||||
|
Style="{StaticResource LabelTextStyle}"
|
||||||
|
Text="{x:Bind BroadcastSlot}"
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
<TextBlock
|
||||||
|
Style="{StaticResource LabelTextStyle}"
|
||||||
|
Text="{x:Bind OwnerAndCategory}"
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
</StackPanel>
|
||||||
|
<Border
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="8,3"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Background="{x:Bind StatusBrush}"
|
||||||
|
CornerRadius="8">
|
||||||
|
<TextBlock
|
||||||
|
FontFamily="Bahnschrift SemiBold"
|
||||||
|
FontSize="12"
|
||||||
|
Foreground="#071013"
|
||||||
|
Text="{x:Bind Status}" />
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
<Border
|
<Border
|
||||||
Height="1"
|
Height="1"
|
||||||
Background="{StaticResource PanelStrokeBrush}" />
|
Background="{StaticResource PanelStrokeBrush}" />
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
## 현재 초기 빌드 범위
|
## 현재 초기 빌드 범위
|
||||||
|
|
||||||
- `Domain`: 큐시트, Scene 템플릿, Scene 데이터 스냅샷, 검수 결과 모델.
|
- `Domain`: 큐시트, Scene 템플릿, Scene 데이터 스냅샷, 검수 결과 모델.
|
||||||
- `Data`: 큐시트, Scene 템플릿, Scene 추출값을 담은 JSON 기반 PoC 샘플.
|
- `Data`: 큐시트, ERP 편성, Scene 템플릿, Scene 추출값을 담은 JSON 기반 PoC 샘플.
|
||||||
- `Services`: 실제 API/Tornado SDK 연결을 받을 인터페이스, JSON 로더, 목 워크스페이스.
|
- `Services`: 실제 API/Tornado SDK 연결을 받을 인터페이스, JSON 로더, 목 워크스페이스.
|
||||||
- `ViewModels`: 운영 콘솔에 표시할 상태 카드, 파이프라인, 매핑표, 검수 큐.
|
- `ViewModels`: 운영 콘솔에 표시할 상태 카드, 파이프라인, 매핑표, 검수 큐.
|
||||||
- `Views`: WinUI 3 기반 첫 화면. 예제 프로젝트와 같은 데스크톱 앱 방향으로 유지.
|
- `Views`: WinUI 3 기반 첫 화면. 예제 프로젝트와 같은 데스크톱 앱 방향으로 유지.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
| ID | RFP 요구사항 | 초기 PoC 반영 상태 | 현재 구현 근거 |
|
| ID | RFP 요구사항 | 초기 PoC 반영 상태 | 현재 구현 근거 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| RFP-01 | 신규 큐시트 플랫폼 API 연동 | JSON Mock 데이터 수신 흐름 구현 | `Data/PocWorkspace.json`, `AutomationWorkspace.CueSheets`, `RefreshCueSheetCommand` |
|
| RFP-01 | 신규 큐시트 플랫폼 API 연동 | JSON Mock 데이터 수신 흐름 구현 | `Data/PocWorkspace.json`, `AutomationWorkspace.CueSheets`, `RefreshCueSheetCommand` |
|
||||||
| RFP-02 | ERP 편성데이터 연동 | 연동 엔드포인트 자리 표시 | `IntegrationEndpointViewModel` |
|
| RFP-02 | ERP 편성데이터 연동 | JSON Mock 편성 데이터 매칭 구현 | `Data/PocWorkspace.json`, `ErpScheduleRecord`, `ScheduleMatches` |
|
||||||
| RFP-03 | CG 포맷 제작 자동화, Data Field Mapping | 큐시트 필드와 Scene 객체 매핑 구현 | `SceneTemplateDefinition`, `PocAutomationEngine.GenerateScene` |
|
| RFP-03 | CG 포맷 제작 자동화, Data Field Mapping | 큐시트 필드와 Scene 객체 매핑 구현 | `SceneTemplateDefinition`, `PocAutomationEngine.GenerateScene` |
|
||||||
| RFP-04 | Tornado Scene API | Scene visible Text/Image 스냅샷 샘플 구현 | `SceneDataSnapshot.ToPreviewJson` |
|
| RFP-04 | Tornado Scene API | Scene visible Text/Image 스냅샷 샘플 구현 | `SceneDataSnapshot.ToPreviewJson` |
|
||||||
| RFP-05 | 큐시트 데이터와 Tornado Scene 연동 관리자 기능 | 매핑표 UI 초안 구현 | `FieldMappings` 화면 |
|
| RFP-05 | 큐시트 데이터와 Tornado Scene 연동 관리자 기능 | 매핑표 UI 초안 구현 | `FieldMappings` 화면 |
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
## 다음 구현 후보
|
## 다음 구현 후보
|
||||||
|
|
||||||
- ERP Mock 데이터를 JSON 파일로 분리하여 실제 편성 API 응답 구조와 맞춘다.
|
- 실제 큐시트/ERP API 응답 DTO와 현재 JSON seed 스키마를 대조한다.
|
||||||
- Tornado2/Tornado3 어댑터를 동일 인터페이스로 유지하고, 버전별 구현체를 분리한다.
|
- Tornado2/Tornado3 어댑터를 동일 인터페이스로 유지하고, 버전별 구현체를 분리한다.
|
||||||
- Scene 내부 비노출 객체를 구분할 메타데이터 규칙을 정의한다.
|
- Scene 내부 비노출 객체를 구분할 메타데이터 규칙을 정의한다.
|
||||||
- 1차 검수 실패 사유를 필드 단위로 누적하고 LLM 요청 후보로 전달한다.
|
- 1차 검수 실패 사유를 필드 단위로 누적하고 LLM 요청 후보로 전달한다.
|
||||||
|
|||||||
Reference in New Issue
Block a user