문서 고도화 다음 단계를 반영해 엑셀 summary와 DOCX 조립 품질을 끌어올렸습니다
핵심 수정사항: - ExcelSkill summary_sheet에 decision_summary, scorecards, sheet_summaries를 추가해 dashboard형 summary 시트를 생성하도록 확장했습니다. - ArtifactQualityReviewService의 workbook 리뷰 입력을 확장해 KPI/decision/detail summary 존재 여부를 품질 점수와 보완 포인트에 반영했습니다. - DocumentAssemblerTool에 style_map 파라미터를 추가해 template 기반 DOCX 조립에서 title/heading/body 문단 스타일을 실제 Word 스타일로 매핑하도록 개선했습니다. - DocumentAssemblerStyleMapTests, ExcelSkillDashboardSummaryTests를 추가하고 기존 ArtifactQualityReviewServiceTests를 갱신했습니다. 검증 결과: - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_next2\\ -p:IntermediateOutputPath=obj\\verify_doc_next2\\ : 경고 0 / 오류 0 - dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ArtifactQualityReviewServiceTests|DocumentAssemblerStyleMapTests|DocumentAssemblerDocxFeaturesTests|DocumentAssemblerSemanticTests|ExcelSkillDashboardSummaryTests|ExcelSkillSummarySheetTests|ExcelSkillExecutiveSummaryLinkTests|ExcelSkillDataValidationTests|ExcelSkillConditionalFormattingTests" -p:OutputPath=bin\\verify_doc_next2_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_next2_tests\\ : 통과 11
This commit is contained in:
@@ -68,6 +68,9 @@ public class ArtifactQualityReviewServiceTests
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false));
|
||||
|
||||
review.Issues.Should().Contain(issue => issue.Message.Contains("summary sheet", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using AxCopilot.Services.Agent;
|
||||
using DocumentFormat.OpenXml.Packaging;
|
||||
using DocumentFormat.OpenXml.Wordprocessing;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class DocumentAssemblerStyleMapTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithStyleMap_ShouldApplyTemplateParagraphStyles()
|
||||
{
|
||||
var workDir = Path.Combine(Path.GetTempPath(), "ax-doc-assemble-stylemap-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(workDir);
|
||||
var templatePath = Path.Combine(workDir, "style-template.docx");
|
||||
|
||||
try
|
||||
{
|
||||
using (var template = WordprocessingDocument.Create(templatePath, DocumentFormat.OpenXml.WordprocessingDocumentType.Document))
|
||||
{
|
||||
var mainPart = template.AddMainDocumentPart();
|
||||
mainPart.Document = new Document(new Body(new Paragraph(new Run(new Text("Template Placeholder")))));
|
||||
|
||||
var stylePart = mainPart.AddNewPart<StyleDefinitionsPart>();
|
||||
stylePart.Styles = new Styles(
|
||||
new Style { Type = StyleValues.Paragraph, StyleId = "DeckTitle", CustomStyle = true },
|
||||
new Style { Type = StyleValues.Paragraph, StyleId = "DeckHeading1", CustomStyle = true },
|
||||
new Style { Type = StyleValues.Paragraph, StyleId = "DeckBody", CustomStyle = true });
|
||||
stylePart.Styles.Save();
|
||||
mainPart.Document.Save();
|
||||
}
|
||||
|
||||
var tool = new DocumentAssemblerTool();
|
||||
var context = new AgentContext
|
||||
{
|
||||
WorkFolder = workDir,
|
||||
Permission = "Auto",
|
||||
OperationMode = "external",
|
||||
};
|
||||
|
||||
var args = JsonDocument.Parse(
|
||||
"""
|
||||
{
|
||||
"path": "assembled-stylemap.docx",
|
||||
"title": "Board Update",
|
||||
"format": "docx",
|
||||
"template_path": "style-template.docx",
|
||||
"style_map": {
|
||||
"title": "DeckTitle",
|
||||
"heading1": "DeckHeading1",
|
||||
"body": "DeckBody"
|
||||
},
|
||||
"sections": [
|
||||
{ "heading": "1. Executive Summary", "level": 1, "content": "<p>Margins improved across key accounts.</p>" }
|
||||
]
|
||||
}
|
||||
""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
|
||||
using var doc = WordprocessingDocument.Open(Path.Combine(workDir, "assembled-stylemap.docx"), false);
|
||||
var paragraphs = doc.MainDocumentPart!.Document.Body!.Elements<Paragraph>().ToList();
|
||||
|
||||
paragraphs.First(paragraph => paragraph.InnerText == "Board Update")
|
||||
.ParagraphProperties?.ParagraphStyleId?.Val?.Value
|
||||
.Should().Be("DeckTitle");
|
||||
paragraphs.First(paragraph => paragraph.InnerText == "1. Executive Summary")
|
||||
.ParagraphProperties?.ParagraphStyleId?.Val?.Value
|
||||
.Should().Be("DeckHeading1");
|
||||
paragraphs.First(paragraph => paragraph.InnerText.Contains("Margins improved"))
|
||||
.ParagraphProperties?.ParagraphStyleId?.Val?.Value
|
||||
.Should().Be("DeckBody");
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(workDir))
|
||||
Directory.Delete(workDir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
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 ExcelSkillDashboardSummaryTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithDashboardSummary_ShouldRenderDecisionAndSheetSummaryBlocks()
|
||||
{
|
||||
var workDir = Path.Combine(Path.GetTempPath(), "ax-xlsx-dashboard-" + 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": "dashboard-review.xlsx",
|
||||
"summary_sheet": {
|
||||
"name": "Summary",
|
||||
"title": "Operating Review",
|
||||
"decision_summary": [
|
||||
{ "label": "Decision Ask", "value": "Approve regional rollout", "owner": "COO" }
|
||||
],
|
||||
"scorecards": [
|
||||
{ "label": "Run-rate", "value": "96%", "status": "On Track", "note": "Ahead of plan" }
|
||||
],
|
||||
"sheet_summaries": [
|
||||
{ "sheet": "Revenue", "status": "Watch", "summary": "SMB margin pressure", "owner": "Sales Ops" }
|
||||
],
|
||||
"highlights": ["Expansion pipeline improved"],
|
||||
"actions": ["Lock next-quarter target"]
|
||||
},
|
||||
"sheets": [
|
||||
{
|
||||
"name": "Revenue",
|
||||
"headers": ["Metric", "Value"],
|
||||
"rows": [["Revenue", 120], ["Margin", 18]]
|
||||
}
|
||||
]
|
||||
}
|
||||
""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
result.Output.Should().Contain("Quality score");
|
||||
|
||||
using var doc = SpreadsheetDocument.Open(Path.Combine(workDir, "dashboard-review.xlsx"), false);
|
||||
var workbookPart = doc.WorkbookPart!;
|
||||
var summarySheet = workbookPart.Workbook.Sheets!.Elements<Sheet>().First(sheet => sheet.Name == "Summary");
|
||||
var summaryPart = (WorksheetPart)workbookPart.GetPartById(summarySheet.Id!);
|
||||
var texts = summaryPart.Worksheet.Descendants<Cell>()
|
||||
.Select(cell => cell.CellValue?.Text)
|
||||
.Where(text => !string.IsNullOrWhiteSpace(text))
|
||||
.ToList();
|
||||
|
||||
texts.Should().Contain("Decision Summary");
|
||||
texts.Should().Contain("Approve regional rollout");
|
||||
texts.Should().Contain("Scorecards");
|
||||
texts.Should().Contain("Run-rate");
|
||||
texts.Should().Contain("Revenue");
|
||||
texts.Should().Contain("SMB margin pressure");
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(workDir))
|
||||
Directory.Delete(workDir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user