문서 품질 critic와 golden 회귀를 고도화해 PPTX·HTML·DOCX·XLSX 마감 품질을 강화한다

- ArtifactQualityReviewService를 확장해 HTML의 board/strategy decision gap, DOCX evidence table·callout 부족, XLSX dashboard trend·variance·sheet summary·headline tile 부족을 개별 이슈로 판정하도록 보강

- ArtifactRepairGuideService와 DeckRepairGuideService를 보강해 decision summary, next steps, evidence table, trend/variance, dashboard tile 등 실제 보강 행동을 바로 제안하는 repair guide를 반환하도록 정리

- ExcelSkill review 입력을 세분화하고 Html/PPT golden 테스트를 strategy brief, PMO steering 시나리오까지 확대해 문서군 golden 회귀 범위를 넓힘

- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_finish_batch\\ -p:IntermediateOutputPath=obj\\verify_doc_finish_batch\\ / dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal (ArtifactQualityReviewServiceTests, ArtifactRepairGuideServiceTests, DeckQualityReviewServiceTests, DeckRepairGuideServiceTests, HtmlSkillGoldenReportTests, PptxSkillGoldenDeckTests, DocxSkillGoldenDocumentTests, ExcelSkillGoldenWorkbookTests) -p:OutputPath=bin\\verify_doc_finish_batch_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_finish_batch_tests\
This commit is contained in:
2026-04-15 10:25:44 +09:00
parent 06540a0e71
commit 2c1926356a
13 changed files with 476 additions and 21 deletions

View File

@@ -17,6 +17,7 @@ public class ArtifactQualityReviewServiceTests
<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>
<div class="decision-summary"></div>
<section class="board-report-panel"></section>
<section class="strategy-brief-panel"></section>
<h2>Recommendation</h2><p>Recommendation text.</p><p>Action support text.</p>
@@ -77,6 +78,9 @@ public class ArtifactQualityReviewServiceTests
false,
false,
false,
false,
false,
false,
false));
review.Issues.Should().Contain(issue => issue.Message.Contains("summary sheet", StringComparison.OrdinalIgnoreCase));
@@ -101,11 +105,60 @@ public class ArtifactQualityReviewServiceTests
false,
false,
false,
true));
false,
false,
false,
false));
review.Issues.Should().Contain(issue => issue.Message.Contains("highlights or actions", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("supporting detail sheets", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("trend or variance formulas", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("KPI, trend, or decision content", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("trend or variance framing", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("summarize each supporting detail sheet", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void ReviewHtml_ShouldFlagDecisionGap_ForBoardAndStrategyPanels()
{
var html =
"""
<h2>Executive Summary</h2><p>Summary text.</p><p>Supporting paragraph.</p>
<section class="board-report-panel"></section>
<section class="strategy-brief-panel"></section>
<div class="comparison-grid"></div>
<div class="roadmap-block"></div>
<h2>Recommendation</h2><p>Recommendation text.</p><p>More detail.</p>
<h2>Appendix</h2><p>Reference detail.</p><p>More detail.</p>
""";
var review = ArtifactQualityReviewService.ReviewHtml("Decision Gap", html, hasCover: false, hasTableOfContents: false, printReady: true);
review.Issues.Should().Contain(issue => issue.Message.Contains("Board-ready report should include a decision summary block", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("Strategy brief should include explicit decisions", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void ReviewStructuredDocument_ShouldRecommendEvidenceTableAndCallouts_ForLongBusinessDoc()
{
var review = ArtifactQualityReviewService.ReviewStructuredDocument(new StructuredDocumentReviewInput(
"Executive Brief",
6,
2400,
0,
1,
0,
0,
0,
true,
true,
true,
true,
true,
true,
true));
review.Issues.Should().Contain(issue => issue.Message.Contains("evidence table", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("callout or highlight blocks", StringComparison.OrdinalIgnoreCase));
}
}

View File

@@ -15,15 +15,16 @@ public class ArtifactRepairGuideServiceTests
["Includes summary sheet"],
[
new ArtifactReviewIssue("Dashboard sheet lacks KPI, trend, or decision content.", ArtifactReviewSeverity.Info),
new ArtifactReviewIssue("Workbook could benefit from a dashboard sheet to summarize multi-sheet trends.", ArtifactReviewSeverity.Info),
new ArtifactReviewIssue("Summary sheet does not link to detail sheets.", ArtifactReviewSeverity.Warning)
new ArtifactReviewIssue("Dashboard sheet lacks trend or variance framing for the supporting sheets.", ArtifactReviewSeverity.Info),
new ArtifactReviewIssue("Dashboard workbook should summarize each supporting detail sheet.", ArtifactReviewSeverity.Info)
]);
var guide = ArtifactRepairGuideService.BuildGuide(review);
guide.Should().Contain("dashboard sheet");
guide.Should().Contain("dashboard");
guide.Should().Contain("core story");
guide.Should().Contain("detail sheets");
guide.Should().Contain("trend");
guide.Should().Contain("sheet summaries");
}
[Fact]
@@ -34,14 +35,14 @@ public class ArtifactRepairGuideServiceTests
70,
["Includes print-ready CSS"],
[
new ArtifactReviewIssue("Print-ready business report would benefit from decision summary or evidence cards.", ArtifactReviewSeverity.Warning),
new ArtifactReviewIssue("Strategy brief would be stronger with a comparison or roadmap block.", ArtifactReviewSeverity.Info)
new ArtifactReviewIssue("Board-ready report should include a decision summary block.", ArtifactReviewSeverity.Warning),
new ArtifactReviewIssue("Strategy brief should include explicit decisions or a decision summary block.", ArtifactReviewSeverity.Warning)
]);
var guide = ArtifactRepairGuideService.BuildGuide(review);
guide.Should().Contain("decision summary");
guide.Should().Contain("comparison or roadmap");
guide.Should().Contain("strategy brief");
}
[Fact]
@@ -52,15 +53,15 @@ public class ArtifactRepairGuideServiceTests
66,
["Includes cover page"],
[
new ArtifactReviewIssue("Long document could benefit from a table of contents.", ArtifactReviewSeverity.Info),
new ArtifactReviewIssue("Template-based styling could improve consistency for a longer document.", ArtifactReviewSeverity.Info),
new ArtifactReviewIssue("Supporting evidence table is limited for a business document.", ArtifactReviewSeverity.Info),
new ArtifactReviewIssue("Key messages would benefit from callout or highlight blocks.", ArtifactReviewSeverity.Info),
new ArtifactReviewIssue("Header or footer metadata is limited for a business document.", ArtifactReviewSeverity.Info)
]);
var guide = ArtifactRepairGuideService.BuildGuide(review);
guide.Should().Contain("table of contents");
guide.Should().Contain("template");
guide.Should().Contain("evidence table");
guide.Should().Contain("callout");
guide.Should().Contain("header and footer");
}
}

View File

@@ -42,7 +42,7 @@ public class DeckQualityReviewServiceTests
{ "layout": "title", "title": "Growth Strategy" },
{ "layout": "executive_summary", "title": "Executive Summary", "headline": "Headline", "summary_points": ["A","B","C"], "recommendation": "Do B" },
{ "layout": "comparison", "title": "Options", "headline": "Compare", "options": [{ "name": "A", "pros": "Fast", "cons": "Shallow", "verdict": "Fastest" }, { "name": "B", "pros": "Balanced", "cons": "Needs alignment", "verdict": "Recommended" }] },
{ "layout": "roadmap", "title": "Roadmap", "headline": "Execute in three phases" },
{ "layout": "roadmap", "title": "Roadmap", "headline": "Execute in three phases", "phases": [{ "title": "Design", "detail": "Lock scope" }, { "title": "Launch", "detail": "Go live" }] },
{ "layout": "recommendation", "title": "Recommendation", "recommendation": "Approve phase-1", "summary_points": ["Reason 1", "Reason 2"] },
{ "layout": "table", "title": "Appendix & Evidence", "headers": ["Evidence","Detail"], "rows": [["Metric","Value"]] }
]
@@ -81,6 +81,7 @@ public class DeckQualityReviewServiceTests
review.Issues.Should().Contain(issue => issue.Message.Contains("Slide 2"));
review.Issues.Should().Contain(issue => issue.Message.Contains("headline is too long"));
review.Issues.Should().Contain(issue => issue.Message.Contains("decision ask", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("comparison slide needs at least two options"));
}
@@ -107,4 +108,21 @@ public class DeckQualityReviewServiceTests
review.Issues.Should().Contain(issue => issue.Message.Contains("Storyline expects a roadmap slide", StringComparison.OrdinalIgnoreCase));
review.Issues.Should().Contain(issue => issue.Message.Contains("Storyline expects appendix or evidence support", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void ReviewDeck_ShouldFlagRecommendationSlideWithoutRationaleOrNextSteps()
{
using var slides = JsonDocument.Parse(
"""
[
{ "layout": "title", "title": "PMO Steering" },
{ "layout": "executive_summary", "title": "Executive Summary", "headline": "Delivery is stable", "summary_points": ["A", "B"], "recommendation": "Proceed" },
{ "layout": "recommendation", "title": "Recommendation", "recommendation": "Proceed" }
]
""");
var review = DeckQualityReviewService.ReviewDeck("Thin Recommendation", slides.RootElement, hasTemplate: false, autoRepairCount: 0);
review.Issues.Should().Contain(issue => issue.Message.Contains("supporting rationale or next steps", StringComparison.OrdinalIgnoreCase));
}
}

View File

@@ -70,4 +70,22 @@ public class DeckRepairGuideServiceTests
guide.Should().Contain("comparison slide");
guide.Should().Contain("storyline");
}
[Fact]
public void BuildGuide_ShouldTranslateDecisionAskAndNextStepGaps()
{
var report = new DeckQualityReport(
60,
[],
[
new DeckReviewIssue("Slide 2: Executive Summary should include a recommendation or decision ask.", DeckReviewSeverity.Info),
new DeckReviewIssue("Slide 3: recommendation slide needs supporting rationale or next steps.", DeckReviewSeverity.Info)
],
[]);
var guide = DeckRepairGuideService.BuildGuide(report);
guide.Should().Contain("decision ask");
guide.Should().Contain("next steps");
}
}

View File

@@ -142,4 +142,121 @@ public class HtmlSkillGoldenReportTests
}
}
}
[Fact]
public async Task ExecuteAsync_WithStrongStrategyBrief_ShouldReturnStableQualitySummary()
{
var workDir = Path.Combine(Path.GetTempPath(), "ax-html-golden-strategy-" + 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": "strategy-golden.html",
"title": "Growth Strategy Brief",
"body": "",
"toc": true,
"print": true,
"cover": {
"title": "Growth Strategy Brief",
"subtitle": "Decision-ready strategy pack",
"author": "AX Copilot",
"date": "2026-04-15"
},
"sections": [
{ "type": "heading", "level": 2, "text": "Executive Summary" },
{ "type": "paragraph", "text": "The strongest near-term growth path is an SMB-first expansion strategy that protects margin while improving sales velocity. The evidence is consistent across retention, commercial efficiency, and onboarding load." },
{ "type": "paragraph", "text": "Leadership should approve the SMB-first sequencing now, then unlock enterprise investment after onboarding capacity stabilizes. This is a sequencing choice, not a strategic reversal." },
{
"type": "strategy_brief",
"title": "Strategic Question",
"strategic_question": "Which segment should be prioritized over the next two quarters?",
"thesis": "SMB-first expansion offers the best balance of growth, margin, and execution risk.",
"implications": [
"Commercial resources should shift toward faster-cycle opportunities.",
"Enterprise capacity should be staged behind onboarding readiness."
],
"decisions": [
"Approve SMB-first sequencing",
"Stage enterprise hiring after week 8"
]
},
{
"type": "comparison",
"title": "Strategic Options",
"items": [
{ "name": "SMB first", "summary": "Fastest near-term growth", "pros": "Strong retention and shorter sales cycle", "cons": "Smaller average deal size", "verdict": "Recommended" },
{ "name": "Balanced mix", "summary": "Diversified growth path", "pros": "Lower concentration risk", "cons": "More operating complexity", "verdict": "Balanced" },
{ "name": "Enterprise first", "summary": "Highest headline upside", "pros": "Largest deal size", "cons": "Longest ramp and heaviest onboarding load", "verdict": "Long-term option" }
]
},
{
"type": "evidence_cards",
"title": "Evidence",
"items": [
{ "title": "Retention", "detail": "SMB retention remains above plan across the last three cohorts.", "source": "Revenue analytics", "tag": "KPI" },
{ "title": "Onboarding load", "detail": "Enterprise onboarding is the main delivery bottleneck.", "source": "Operations review", "tag": "Ops" }
]
},
{
"type": "roadmap",
"title": "Execution Roadmap",
"phases": [
{ "title": "Refocus", "detail": "Reset segment priorities and revenue targets", "timeline": "Weeks 1-2", "owner": "Strategy" },
{ "title": "Enable", "detail": "Align messaging, pricing, and onboarding", "timeline": "Weeks 3-6", "owner": "Sales + Ops" },
{ "title": "Scale", "detail": "Expand channel coverage and monitor retention", "timeline": "Weeks 7-12", "owner": "GM" }
]
},
{
"type": "decision_summary",
"title": "Decision Summary",
"decision": "Approve the SMB-first growth plan and stage enterprise investment after onboarding capacity stabilizes.",
"rationale": "The SMB-first path captures faster growth with lower delivery complexity.",
"actions": [
"Reset next-quarter targets",
"Lock the SMB enablement backlog",
"Review enterprise readiness at week 8"
]
},
{ "type": "heading", "level": 2, "text": "Appendix" },
{ "type": "paragraph", "text": "The appendix contains source notes and supporting assumptions behind the growth model. It preserves traceability from the recommended decision to the underlying evidence." }
]
}
""").RootElement;
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
result.Success.Should().BeTrue();
result.Output.Should().Contain("Quality score");
result.Output.Should().Contain("Needs work: none");
result.Output.Should().Contain("Repair guide: none");
var html = File.ReadAllText(Path.Combine(workDir, "strategy-golden.html"));
html.Should().Contain("strategy-brief-panel");
html.Should().Contain("decision-summary");
html.Should().Contain("comparison-grid");
html.Should().Contain("evidence-cards");
}
finally
{
try
{
if (Directory.Exists(workDir))
Directory.Delete(workDir, true);
}
catch
{
}
}
}
}

View File

@@ -237,4 +237,131 @@ public class PptxSkillGoldenDeckTests
}
}
}
[Fact]
public async Task ExecuteAsync_WithStrongPmoSteeringDeck_ShouldRemainFreeOfSlideAlerts()
{
var workDir = Path.Combine(Path.GetTempPath(), "ax-pptx-golden-pmo-" + 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": "pmo-golden.pptx",
"theme": "professional",
"template_pack": "pmo",
"audience": "Transformation steering committee",
"objective": "Confirm the phase-2 PMO steering decision",
"decision_ask": "Approve the next wave and keep the governance cadence",
"storyline": ["Executive Summary", "Issue Tree", "Risk Overview", "Roadmap", "Recommendation", "Appendix"],
"slides": [
{
"layout": "title",
"title": "PMO Steering Pack"
},
{
"layout": "executive_summary",
"title": "Executive Summary",
"headline": "Phase-2 can proceed with controlled delivery risk and a stable governance model.",
"summary_points": [
"The current PMO cadence already highlights the few issues that matter most for the next wave.",
"The major delivery risk is onboarding bottlenecks, not design ambiguity.",
"The decision is now about release timing and governance discipline."
],
"recommendation": "Approve the next wave and keep the PMO steering cadence intact.",
"kpis": [
{ "label": "Milestone Health", "value": "92%", "trend": "on track", "note": "core plan stable" },
{ "label": "Risk Closure", "value": "78%", "trend": "improving", "note": "weekly review" },
{ "label": "Budget", "value": "98%", "trend": "plan", "note": "within guardrails" }
]
},
{
"layout": "issue_tree",
"title": "Issue Tree",
"issues": [
"Onboarding capacity is the main delivery bottleneck.",
"Regional readiness varies by manager capability.",
"Decision latency increases when risk owners are unclear."
],
"implications": [
"Protect the launch window with tighter staffing rules.",
"Keep manager enablement inside the PMO workplan.",
"Confirm named owners for the top-three risk items."
]
},
{
"layout": "risk_heatmap",
"title": "Risk Overview",
"risks": [
{ "title": "Onboarding bottleneck", "impact": "High", "likelihood": "Medium", "mitigation": "Stage wave-2 staffing" },
{ "title": "Regional readiness gap", "impact": "Medium", "likelihood": "Medium", "mitigation": "Run manager enablement" }
]
},
{
"layout": "roadmap",
"title": "Roadmap",
"headline": "Run the next wave through three controlled PMO checkpoints.",
"phases": [
{ "title": "Approve", "detail": "Lock funding, risk owners, and wave scope", "timeline": "Week 1", "owner": "SteerCo" },
{ "title": "Launch", "detail": "Start the next wave and monitor onboarding load", "timeline": "Weeks 2-6", "owner": "PMO" },
{ "title": "Review", "detail": "Confirm milestone health and risk closure", "timeline": "Week 8", "owner": "Exec sponsors" }
]
},
{
"layout": "recommendation",
"title": "Recommendation",
"headline": "Approve the next wave and keep governance cadence intact.",
"recommendation": "Approve the next wave, keep weekly PMO reviews, and assign named owners to the top-three risks.",
"summary_points": [
"The current evidence is sufficient to proceed without delaying the launch window.",
"Governance continuity is the main control lever for delivery quality."
],
"next_steps": [
"Approve funding and scope",
"Confirm risk owners",
"Start weekly PMO review for wave 2"
]
},
{
"layout": "appendix_evidence",
"title": "Appendix & Evidence",
"evidence": [
{ "title": "Milestone trend", "detail": "Core milestones remain above 90% health", "source": "PMO tracker" },
{ "title": "Budget status", "detail": "Program remains inside the approved guardrails", "source": "Finance review" }
]
}
]
}
""").RootElement;
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
result.Success.Should().BeTrue();
File.Exists(Path.Combine(workDir, "pmo-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
{
}
}
}
}