문서 생성 고도화 1차: 네이티브 워드·엑셀·HTML 경로 정렬 및 품질 보강
- Word/Excel/HTML 스킬을 Python 우회 중심에서 AX 네이티브 문서 도구 우선 경로로 재작성했습니다. - DocumentPlannerTool의 보고서·제안서·분석 문서 아웃라인을 Executive Summary, Business Case, Decision Ask, Appendix 중심의 업무형 구조로 확장했습니다. - DocumentAssemblerTool의 DOCX 조립 경로에서 표·목록·콜아웃·소제목 같은 HTML/Markdown 구조를 더 보존하도록 개선했습니다. - ExcelSkill에 summary_sheet를 추가해 KPI·핵심 인사이트·후속 과제를 담은 요약 시트를 상세 데이터 시트 앞에 생성할 수 있게 했습니다. - HtmlSkill에 comparison, roadmap, matrix 구조화 섹션을 추가하고 sections 중심 호출 스키마를 정리했습니다. - DocumentAssemblerSemanticTests, ExcelSkillSummarySheetTests, HtmlSkillConsultingSectionsTests, DocumentPlannerBusinessDocumentTests를 추가했습니다. - 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_phase1\\ -p:IntermediateOutputPath=obj\\verify_doc_phase1\ - 검증: dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter 문서_고도화_테스트_5건 -p:OutputPath=bin\\verify_doc_phase1_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_phase1_tests\
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using AxCopilot.Services.Agent;
|
||||
using DocumentFormat.OpenXml.Packaging;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class DocumentAssemblerSemanticTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ForDocx_ShouldPreserveTablesListsAndCallouts()
|
||||
{
|
||||
var workDir = Path.Combine(Path.GetTempPath(), "ax-doc-assemble-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(workDir);
|
||||
|
||||
try
|
||||
{
|
||||
var tool = new DocumentAssemblerTool();
|
||||
var context = new AgentContext
|
||||
{
|
||||
WorkFolder = workDir,
|
||||
Permission = "Auto",
|
||||
OperationMode = "external",
|
||||
};
|
||||
|
||||
var args = JsonDocument.Parse(
|
||||
"""
|
||||
{
|
||||
"path": "assembled.docx",
|
||||
"title": "운영 개선 보고서",
|
||||
"format": "docx",
|
||||
"sections": [
|
||||
{
|
||||
"heading": "1. Executive Summary",
|
||||
"level": 1,
|
||||
"content": "<p>핵심 메시지 요약입니다.</p><ul><li>첫 항목</li><li>둘째 항목</li></ul><table><tr><th>구분</th><th>값</th></tr><tr><td>매출</td><td>120</td></tr></table><div class=\"callout-info\"><strong>핵심 메모</strong><p>즉시 조치가 필요합니다.</p></div>"
|
||||
}
|
||||
]
|
||||
}
|
||||
""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
var outputPath = Path.Combine(workDir, "assembled.docx");
|
||||
File.Exists(outputPath).Should().BeTrue();
|
||||
|
||||
using var doc = WordprocessingDocument.Open(outputPath, false);
|
||||
var body = doc.MainDocumentPart?.Document.Body;
|
||||
body.Should().NotBeNull();
|
||||
body!.Descendants<DocumentFormat.OpenXml.Wordprocessing.Table>().Should().NotBeEmpty();
|
||||
body.InnerText.Should().Contain("첫 항목");
|
||||
body.InnerText.Should().Contain("즉시 조치가 필요합니다.");
|
||||
body.InnerText.Should().Contain("매출");
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(workDir))
|
||||
Directory.Delete(workDir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using AxCopilot.Services.Agent;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class DocumentPlannerBusinessDocumentTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ForProposal_ShouldReturnConsultingProposalStructure()
|
||||
{
|
||||
var tool = new DocumentPlannerTool();
|
||||
var context = new AgentContext
|
||||
{
|
||||
WorkFolder = Path.GetTempPath(),
|
||||
Permission = "Auto",
|
||||
OperationMode = "external",
|
||||
};
|
||||
|
||||
var args = JsonDocument.Parse(
|
||||
"""
|
||||
{
|
||||
"topic": "2026 운영 혁신 제안서",
|
||||
"document_type": "proposal",
|
||||
"target_pages": 8
|
||||
}
|
||||
""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
result.Output.Should().Contain("Executive Summary");
|
||||
result.Output.Should().Contain("Business Case");
|
||||
result.Output.Should().Contain("Risks & Mitigations");
|
||||
result.Output.Should().Contain("Decision Ask");
|
||||
result.Output.Should().Contain("Appendix");
|
||||
}
|
||||
}
|
||||
83
src/AxCopilot.Tests/Services/ExcelSkillSummarySheetTests.cs
Normal file
83
src/AxCopilot.Tests/Services/ExcelSkillSummarySheetTests.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using AxCopilot.Services.Agent;
|
||||
using DocumentFormat.OpenXml.Packaging;
|
||||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class ExcelSkillSummarySheetTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithSummarySheet_ShouldCreateExecutiveSheet()
|
||||
{
|
||||
var workDir = Path.Combine(Path.GetTempPath(), "ax-xlsx-summary-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(workDir);
|
||||
|
||||
try
|
||||
{
|
||||
var tool = new ExcelSkill();
|
||||
var context = new AgentContext
|
||||
{
|
||||
WorkFolder = workDir,
|
||||
Permission = "Auto",
|
||||
OperationMode = "external",
|
||||
};
|
||||
|
||||
var args = JsonDocument.Parse(
|
||||
"""
|
||||
{
|
||||
"path": "review.xlsx",
|
||||
"sheet_name": "Detail",
|
||||
"headers": ["항목", "값", "증감"],
|
||||
"rows": [
|
||||
["Revenue", "120", "12%"],
|
||||
["Cost", "80", "-4%"]
|
||||
],
|
||||
"summary_sheet": {
|
||||
"name": "Summary",
|
||||
"title": "2026 운영 리뷰",
|
||||
"subtitle": "핵심 KPI와 후속 과제",
|
||||
"kpis": [
|
||||
{ "label": "Revenue", "value": "120", "trend": "YoY +12%", "note": "Strong" }
|
||||
],
|
||||
"highlights": ["수익성 개선", "비용 효율화"],
|
||||
"actions": ["우선 과제 확정", "월간 리뷰 체계화"]
|
||||
}
|
||||
}
|
||||
""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
var outputPath = Path.Combine(workDir, "review.xlsx");
|
||||
File.Exists(outputPath).Should().BeTrue();
|
||||
|
||||
using var doc = SpreadsheetDocument.Open(outputPath, false);
|
||||
var sheets = doc.WorkbookPart?.Workbook.Sheets?.Elements<Sheet>().ToList();
|
||||
sheets.Should().NotBeNull();
|
||||
var actualSheets = sheets ?? throw new InvalidOperationException("Workbook sheets were not created.");
|
||||
actualSheets.Should().HaveCount(2);
|
||||
actualSheets[0].Name?.Value.Should().Be("Summary");
|
||||
actualSheets[1].Name?.Value.Should().Be("Detail");
|
||||
|
||||
var summaryWorksheet = (WorksheetPart)doc.WorkbookPart!.GetPartById(actualSheets[0].Id!);
|
||||
var firstCell = summaryWorksheet.Worksheet.Descendants<Cell>().FirstOrDefault(c => c.CellReference == "A1");
|
||||
firstCell.Should().NotBeNull();
|
||||
firstCell!.CellValue!.Text.Should().Be("2026 운영 리뷰");
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(workDir))
|
||||
Directory.Delete(workDir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using AxCopilot.Services.Agent;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class HtmlSkillConsultingSectionsTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithConsultingSections_ShouldRenderAdvancedBlocks()
|
||||
{
|
||||
var workDir = Path.Combine(Path.GetTempPath(), "ax-html-consulting-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(workDir);
|
||||
|
||||
try
|
||||
{
|
||||
var tool = new HtmlSkill();
|
||||
var context = new AgentContext
|
||||
{
|
||||
WorkFolder = workDir,
|
||||
Permission = "Auto",
|
||||
OperationMode = "external",
|
||||
};
|
||||
|
||||
var args = JsonDocument.Parse(
|
||||
"""
|
||||
{
|
||||
"path": "consulting.html",
|
||||
"title": "전략 리뷰",
|
||||
"body": "",
|
||||
"sections": [
|
||||
{
|
||||
"type": "comparison",
|
||||
"title": "옵션 비교",
|
||||
"items": [
|
||||
{ "name": "Option A", "summary": "빠른 실행", "pros": "도입이 쉽다", "cons": "확장성 제약", "verdict": "Fast" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "roadmap",
|
||||
"title": "실행 로드맵",
|
||||
"phases": [
|
||||
{ "title": "Phase 1", "detail": "진단 및 설계", "timeline": "0-30일", "owner": "PMO" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "matrix",
|
||||
"title": "우선순위 매트릭스",
|
||||
"quadrants": [
|
||||
{ "title": "Quick Wins", "items": ["자동화 과제", "리포트 표준화"] },
|
||||
{ "title": "Strategic Bets", "items": ["플랫폼 재구성"] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
var html = File.ReadAllText(Path.Combine(workDir, "consulting.html"));
|
||||
html.Should().Contain("comparison-grid");
|
||||
html.Should().Contain("roadmap-block");
|
||||
html.Should().Contain("matrix-grid");
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(workDir))
|
||||
Directory.Delete(workDir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user