문서 대시보드·DOCX 스타일맵·PPT 골든 회귀를 고도화하고 검증 추가

- ExcelSkill에 summary_sheet 기반 Dashboard 시트 생성을 추가해 Summary -> Dashboard -> Detail 시트 흐름을 지원
- ArtifactQualityReviewService workbook 리뷰에 dashboard 존재 여부를 반영하고 multi-sheet workbook 보완 포인트를 강화
- DocumentAssemblerTool style_map 범위를 cover_subtitle/callout/table_header까지 확장해 템플릿 기반 DOCX 조립 품질을 개선
- Excel/DOCX/PPT 회귀 테스트를 확장하고 PptxSkillGoldenDeckTests를 추가해 strong deck 품질 기준을 고정
- README.md와 docs/DEVELOPMENT.md에 2026-04-14 23:25 (KST) 기준 작업 이력과 검증 명령을 반영

검증 결과
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_next4\\ -p:IntermediateOutputPath=obj\\verify_doc_next4\\ : 경고 0 / 오류 0
- dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter ArtifactQualityReviewServiceTests|DocumentAssemblerStyleMapTests|DocumentAssemblerDocxFeaturesTests|DocumentAssemblerSemanticTests|ExcelSkillDashboardSummaryTests|ExcelSkillSummarySheetTests|ExcelSkillExecutiveSummaryLinkTests|ExcelSkillDataValidationTests|ExcelSkillConditionalFormattingTests|HtmlSkillPrintFrameTests|HtmlSkillConsultingSectionsTests|DeckQualityReviewServiceTests|PptxSkillAutoRepairTests|PptxSkillConsultingDeckTests|PptxSkillGoldenDeckTests -p:OutputPath=bin\\verify_doc_next4_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_next4_tests\\ : 통과 20
This commit is contained in:
2026-04-14 23:26:59 +09:00
parent 2e36f2fef1
commit 116c420bf6
9 changed files with 369 additions and 35 deletions

View File

@@ -71,6 +71,7 @@ public class ArtifactQualityReviewServiceTests
false,
false,
false,
false,
false));
review.Issues.Should().Contain(issue => issue.Message.Contains("summary sheet", StringComparison.OrdinalIgnoreCase));

View File

@@ -28,7 +28,10 @@ public class DocumentAssemblerStyleMapTests
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 });
new Style { Type = StyleValues.Paragraph, StyleId = "DeckBody", CustomStyle = true },
new Style { Type = StyleValues.Paragraph, StyleId = "DeckSubtitle", CustomStyle = true },
new Style { Type = StyleValues.Paragraph, StyleId = "DeckCallout", CustomStyle = true },
new Style { Type = StyleValues.Paragraph, StyleId = "DeckTableHeader", CustomStyle = true });
stylePart.Styles.Save();
mainPart.Document.Save();
}
@@ -48,13 +51,17 @@ public class DocumentAssemblerStyleMapTests
"title": "Board Update",
"format": "docx",
"template_path": "style-template.docx",
"cover_subtitle": "Quarterly Steering Pack",
"style_map": {
"title": "DeckTitle",
"heading1": "DeckHeading1",
"body": "DeckBody"
"body": "DeckBody",
"cover_subtitle": "DeckSubtitle",
"callout": "DeckCallout",
"table_header": "DeckTableHeader"
},
"sections": [
{ "heading": "1. Executive Summary", "level": 1, "content": "<p>Margins improved across key accounts.</p>" }
{ "heading": "1. Executive Summary", "level": 1, "content": "<p>Margins improved across key accounts.</p><div class=\"callout-info\">Focus on margin recovery</div><table><tr><th>Metric</th><th>Value</th></tr><tr><td>Margin</td><td>18%</td></tr></table>" }
]
}
""").RootElement;
@@ -69,12 +76,28 @@ public class DocumentAssemblerStyleMapTests
paragraphs.First(paragraph => paragraph.InnerText == "Board Update")
.ParagraphProperties?.ParagraphStyleId?.Val?.Value
.Should().Be("DeckTitle");
paragraphs.First(paragraph => paragraph.InnerText == "Quarterly Steering Pack")
.ParagraphProperties?.ParagraphStyleId?.Val?.Value
.Should().Be("DeckSubtitle");
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");
paragraphs.First(paragraph => paragraph.InnerText.Contains("Focus on margin recovery"))
.ParagraphProperties?.ParagraphStyleId?.Val?.Value
.Should().Be("DeckCallout");
var firstHeaderParagraph = doc.MainDocumentPart.Document.Body!
.Descendants<Table>()
.First()
.Descendants<TableRow>()
.First()
.Descendants<Paragraph>()
.First();
firstHeaderParagraph.ParagraphProperties?.ParagraphStyleId?.Val?.Value
.Should().Be("DeckTableHeader");
}
finally
{

View File

@@ -32,6 +32,7 @@ public class ExcelSkillDashboardSummaryTests
"path": "dashboard-review.xlsx",
"summary_sheet": {
"name": "Summary",
"dashboard_sheet_name": "Dashboard",
"title": "Operating Review",
"decision_summary": [
{ "label": "Decision Ask", "value": "Approve regional rollout", "owner": "COO" }
@@ -65,6 +66,9 @@ public class ExcelSkillDashboardSummaryTests
using var doc = SpreadsheetDocument.Open(Path.Combine(workDir, "dashboard-review.xlsx"), false);
var workbookPart = doc.WorkbookPart!;
workbookPart.Workbook.Sheets!.Elements<Sheet>().Select(sheet => sheet.Name!.Value)
.Should().Contain(["Summary", "Dashboard", "Revenue"]);
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>()
@@ -80,6 +84,16 @@ public class ExcelSkillDashboardSummaryTests
texts.Should().Contain("-10");
texts.Should().Contain("Revenue");
texts.Should().Contain("SMB margin pressure");
var dashboardSheet = workbookPart.Workbook.Sheets!.Elements<Sheet>().First(sheet => sheet.Name == "Dashboard");
var dashboardPart = (WorksheetPart)workbookPart.GetPartById(dashboardSheet.Id!);
var dashboardTexts = dashboardPart.Worksheet.Descendants<Cell>()
.Select(cell => cell.CellValue?.Text)
.Where(text => !string.IsNullOrWhiteSpace(text))
.ToList();
dashboardTexts.Should().Contain("Operating Review Dashboard");
dashboardTexts.Should().Contain("Linked Detail Sheets");
dashboardTexts.Should().Contain("Open: Revenue");
}
finally
{

View File

@@ -0,0 +1,124 @@
using System.IO;
using System.Text.Json;
using AxCopilot.Services.Agent;
using FluentAssertions;
using Xunit;
namespace AxCopilot.Tests.Services;
public class PptxSkillGoldenDeckTests
{
[Fact]
public async Task ExecuteAsync_WithStrongBoardDeck_ShouldReturnStableQualitySummary()
{
var workDir = Path.Combine(Path.GetTempPath(), "ax-pptx-golden-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(workDir);
try
{
var tool = new PptxSkill();
var context = new AgentContext
{
WorkFolder = workDir,
Permission = "Auto",
OperationMode = "external",
};
var args = JsonDocument.Parse(
"""
{
"path": "board-golden.pptx",
"theme": "professional",
"audience": "Executive committee",
"objective": "Approve the phase-1 operating model rollout",
"decision_ask": "Approve the phase-1 rollout and funding release",
"slides": [
{
"layout": "title",
"title": "Operating Model Refresh"
},
{
"layout": "executive_summary",
"title": "Executive Summary",
"headline": "Phase-1 rollout can improve service consistency while protecting margin.",
"summary_points": [
"Three pilot functions account for most process variation and rework.",
"Phase-1 can be delivered without slowing current-quarter commitments.",
"The operating model change is expected to reduce handoff delay by 18%."
],
"recommendation": "Approve the phase-1 rollout and lock the pilot governance model.",
"kpis": [
{ "label": "Cycle Time", "value": "-18%", "trend": "target", "note": "handoff simplification" },
{ "label": "Margin", "value": "+2.4pt", "trend": "run-rate", "note": "mix and rework" },
{ "label": "On-time Delivery", "value": "96%", "trend": "pilot", "note": "stable" }
]
},
{
"layout": "comparison",
"title": "Options",
"headline": "A phased rollout balances speed, control, and adoption risk.",
"options": [
{ "name": "Pilot only", "pros": "Lowest delivery risk", "cons": "Value capture is delayed", "verdict": "Too cautious" },
{ "name": "Phased rollout", "pros": "Balanced speed and control", "cons": "Requires PMO discipline", "verdict": "Recommended" },
{ "name": "Full rollout", "pros": "Fastest value capture", "cons": "High adoption risk", "verdict": "Too disruptive" }
]
},
{
"layout": "roadmap",
"title": "Roadmap",
"headline": "Roll out in three waves with explicit governance checkpoints.",
"phases": [
{ "title": "Design", "detail": "Finalize scope, KPIs, and governance", "timeline": "Weeks 1-3", "owner": "PMO" },
{ "title": "Pilot", "detail": "Launch pilot functions and refine playbooks", "timeline": "Weeks 4-8", "owner": "Operations" },
{ "title": "Scale", "detail": "Expand to the remaining regions", "timeline": "Weeks 9-14", "owner": "Business" }
]
},
{
"layout": "recommendation",
"title": "Recommendation",
"recommendation": "Approve phase-1 rollout, assign PMO governance, and release pilot funding.",
"summary_points": [
"The phased path protects execution quality while capturing measurable value in-quarter.",
"The pilot governance model can be reused for the scale phase.",
"The decision can be taken now because the key delivery risks are already bounded."
],
"next_steps": [
"Confirm phase-1 scope this week",
"Launch pilot governance cadence next week",
"Review pilot performance at week 8"
]
},
{
"layout": "table",
"title": "Appendix & Evidence",
"headers": ["Evidence", "Detail"],
"rows": [
["Process review", "Pilot teams show the highest rework and handoff load."],
["Financial impact", "Run-rate margin lift is driven by lower rework and better prioritization."]
]
}
]
}
""").RootElement;
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
result.Success.Should().BeTrue();
File.Exists(Path.Combine(workDir, "board-golden.pptx")).Should().BeTrue();
result.Output.Should().Contain("PPT quality");
result.Output.Should().NotContain("Slide alerts:");
result.Output.Should().Contain("Needs work: none");
}
finally
{
try
{
if (Directory.Exists(workDir))
Directory.Delete(workDir, true);
}
catch
{
}
}
}
}