문서 생성 품질 게이트와 산출물 고도화 2차 반영
공통 ArtifactQualityReviewService를 추가해 HTML, DOCX, XLSX 결과물에 로컬 품질 점수와 보완 포인트를 부여했습니다. DocxSkill에 template_path, cover_subtitle, cover_meta, toc 흐름을 붙여 템플릿 기반 문서와 커버/목차 생성을 강화했고, Excel summary sheet에는 detail sheet 링크와 workbook review를 연결했습니다. HtmlSkill도 결과 요약에 품질 리뷰를 포함하도록 보강했습니다. executive-brief, kpi-workbook, board-report-html 번들 스킬을 추가했고, ArtifactQualityReviewServiceTests, DocxSkillTemplateFeaturesTests, ExcelSkillExecutiveSummaryLinkTests를 포함한 관련 테스트를 보강했습니다. 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_doc_phase2\ -p:IntermediateOutputPath=obj\verify_doc_phase2\ (경고 0 / 오류 0) 검증: dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter ArtifactQualityReviewServiceTests|DocxSkillTemplateFeaturesTests|ExcelSkillExecutiveSummaryLinkTests|DocumentAssemblerSemanticTests|DocumentPlannerBusinessDocumentTests|HtmlSkillConsultingSectionsTests|ExcelSkillSummarySheetTests -p:OutputPath=bin\verify_doc_phase2_tests\ -p:IntermediateOutputPath=obj\verify_doc_phase2_tests\ (통과 9)
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
using AxCopilot.Services.Agent;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class ArtifactQualityReviewServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReviewHtml_ShouldDetectRichBusinessStructure()
|
||||
{
|
||||
var html =
|
||||
"""
|
||||
<h2>Executive Summary</h2><p>Summary text.</p><p>Another supporting paragraph.</p>
|
||||
<h2>Current State</h2><p>Current state detail.</p><p>Evidence paragraph.</p>
|
||||
<div class="callout-info">Important message</div>
|
||||
<table><tr><th>Metric</th><th>Value</th></tr><tr><td>NPS</td><td>61</td></tr></table>
|
||||
<div class="comparison-grid"></div>
|
||||
<div class="roadmap-block"></div>
|
||||
<h2>Recommendation</h2><p>Recommendation text.</p><p>Action support text.</p>
|
||||
<h2>Appendix</h2><p>Reference detail.</p><p>More detail.</p>
|
||||
""";
|
||||
|
||||
var review = ArtifactQualityReviewService.ReviewHtml("Board Report", html, hasCover: true, hasTableOfContents: true, printReady: true);
|
||||
|
||||
review.Score.Should().BeGreaterThan(75);
|
||||
review.Strengths.Should().Contain(s => s.Contains("커버") || s.Contains("목차"));
|
||||
review.Issues.Should().NotContain(i => i.Severity == ArtifactReviewSeverity.Critical);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReviewStructuredDocument_ShouldFlagMissingExecutiveSections()
|
||||
{
|
||||
var review = ArtifactQualityReviewService.ReviewStructuredDocument(new StructuredDocumentReviewInput(
|
||||
"Ops Memo",
|
||||
2,
|
||||
600,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false));
|
||||
|
||||
review.Issues.Should().Contain(i => i.Message.Contains("Executive Summary"));
|
||||
review.Issues.Should().Contain(i => i.Message.Contains("권고안"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReviewWorkbook_ShouldFlagMissingSummaryForMultiSheet()
|
||||
{
|
||||
var review = ArtifactQualityReviewService.ReviewWorkbook(new WorkbookReviewInput(
|
||||
"PMO Tracker",
|
||||
3,
|
||||
3,
|
||||
40,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false));
|
||||
|
||||
review.Issues.Should().Contain(i => i.Message.Contains("요약 시트"));
|
||||
review.Score.Should().BeLessThan(80);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
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 DocxSkillTemplateFeaturesTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ShouldCreateDocx_WithTemplateCoverAndToc()
|
||||
{
|
||||
var workDir = Path.Combine(Path.GetTempPath(), "ax-docx-template-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(workDir);
|
||||
|
||||
try
|
||||
{
|
||||
var templatePath = Path.Combine(workDir, "template.docx");
|
||||
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 Root")))));
|
||||
mainPart.Document.Save();
|
||||
}
|
||||
|
||||
var tool = new DocxSkill();
|
||||
var context = new AgentContext
|
||||
{
|
||||
WorkFolder = workDir,
|
||||
Permission = "Auto",
|
||||
OperationMode = "external",
|
||||
};
|
||||
|
||||
var args = JsonDocument.Parse(
|
||||
"""
|
||||
{
|
||||
"path": "executive-brief.docx",
|
||||
"title": "Executive Brief",
|
||||
"template_path": "template.docx",
|
||||
"cover_subtitle": "Q2 Operating Review",
|
||||
"cover_meta": ["Prepared for Steering Committee", "Confidential"],
|
||||
"toc": true,
|
||||
"sections": [
|
||||
{ "heading": "Executive Summary", "body": "Summary line one.\nSummary line two.", "level": 1 },
|
||||
{ "heading": "Recommendation", "body": "Recommendation details.", "level": 1 }
|
||||
]
|
||||
}
|
||||
""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
var outputPath = Path.Combine(workDir, "executive-brief.docx");
|
||||
File.Exists(outputPath).Should().BeTrue();
|
||||
|
||||
using var doc = WordprocessingDocument.Open(outputPath, false);
|
||||
doc.MainDocumentPart!.Document.Body!.InnerText.Should().Contain("Q2 Operating Review");
|
||||
doc.MainDocumentPart.Document.Body.InnerText.Should().Contain("Prepared for Steering Committee");
|
||||
doc.MainDocumentPart.Document.Body.Descendants<FieldCode>().Should().Contain(f => f.Text.Contains("TOC"));
|
||||
}
|
||||
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 DocumentFormat.OpenXml.Packaging;
|
||||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class ExcelSkillExecutiveSummaryLinkTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ShouldCreateSummarySheetWithDetailLinks()
|
||||
{
|
||||
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": "ops-review.xlsx",
|
||||
"summary_sheet": {
|
||||
"name": "Summary",
|
||||
"title": "Ops Review",
|
||||
"highlights": ["Margin improved", "Backlog stable"],
|
||||
"actions": ["Expand automation", "Tighten forecast"]
|
||||
},
|
||||
"sheets": [
|
||||
{
|
||||
"name": "Revenue",
|
||||
"headers": ["Metric", "Value"],
|
||||
"rows": [["Revenue", 120], ["Margin", 18], ["Delta", "=B2-B3"]]
|
||||
},
|
||||
{
|
||||
"name": "Pipeline",
|
||||
"headers": ["Stage", "Count"],
|
||||
"rows": [["Qualified", 14], ["Proposal", 8]]
|
||||
}
|
||||
]
|
||||
}
|
||||
""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
var outputPath = Path.Combine(workDir, "ops-review.xlsx");
|
||||
File.Exists(outputPath).Should().BeTrue();
|
||||
|
||||
using var doc = SpreadsheetDocument.Open(outputPath, false);
|
||||
var workbookPart = doc.WorkbookPart;
|
||||
workbookPart.Should().NotBeNull();
|
||||
var summarySheet = workbookPart!.Workbook.Sheets!.Elements<Sheet>().First(s => s.Name == "Summary");
|
||||
var summaryPart = (WorksheetPart)workbookPart.GetPartById(summarySheet.Id!);
|
||||
summaryPart.Worksheet.Elements<Hyperlinks>().Should().ContainSingle();
|
||||
summaryPart.Worksheet.Descendants<Hyperlink>().Count().Should().BeGreaterOrEqualTo(2);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(workDir))
|
||||
Directory.Delete(workDir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user