- SqlAnalysisService에 script intent, dependency, review focus 계산을 추가해 migration/seed/reporting SQL의 위험도와 검토 포인트를 더 정확히 안내하도록 개선했습니다. - HtmlSkill에 decision_matrix, metric_strip 섹션을 추가하고 ArtifactQualityReviewService/ArtifactRepairGuideService에서 board·strategy 문서의 의사결정 구조와 KPI 연결 부족을 더 정밀하게 진단하도록 강화했습니다. - DeckQualityReviewService와 DeckRepairGuideService를 확장해 executive summary headline, comparison trade-off, roadmap milestone, chart takeaway, KPI context 부족을 추가로 감지하고 보정 가이드를 반환하도록 정리했습니다. - WorkspaceContextGenerator와 CodeLanguageCatalog를 업데이트해 SQL 저장소에서 SQL Review Focus와 확장된 workflow summary를 제공하도록 맞췄고, README/DEVELOPMENT/NEXT_ROADMAP에 2026-04-15 11:36 (KST) 기준 이력을 반영했습니다. 검증 결과 - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_code_sql_doc_final\\ -p:IntermediateOutputPath=obj\\verify_code_sql_doc_final\\ : 경고 0 / 오류 0 - dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "SqlDialectDetectorTests|SqlAnalysisServiceTests|CodeLanguageCatalogTests|WorkspaceContextGeneratorTests|ArtifactQualityReviewServiceTests|ArtifactRepairGuideServiceTests|DeckQualityReviewServiceTests|HtmlSkillConsultingSectionsTests" -p:OutputPath=bin\\verify_code_sql_doc_final_tests\\ -p:IntermediateOutputPath=obj\\verify_code_sql_doc_final_tests\\ : 통과 62
171 lines
7.9 KiB
C#
171 lines
7.9 KiB
C#
using System.Text.Json;
|
|
using AxCopilot.Services.Agent;
|
|
using FluentAssertions;
|
|
using Xunit;
|
|
|
|
namespace AxCopilot.Tests.Services;
|
|
|
|
public class DeckQualityReviewServiceTests
|
|
{
|
|
[Fact]
|
|
public void ReviewDeck_ShouldFlagMissingExecutiveSummaryAndRecommendation()
|
|
{
|
|
using var slides = JsonDocument.Parse(
|
|
"""
|
|
[
|
|
{
|
|
"layout": "content",
|
|
"title": "Current State",
|
|
"body": "Point 1\nPoint 2\nPoint 3\nPoint 4\nPoint 5\nPoint 6\nPoint 7"
|
|
},
|
|
{
|
|
"layout": "content",
|
|
"title": "Findings",
|
|
"body": "Observation A\nObservation B\nObservation C"
|
|
}
|
|
]
|
|
""");
|
|
|
|
var review = DeckQualityReviewService.ReviewDeck("Weak Deck", slides.RootElement, hasTemplate: false, autoRepairCount: 0);
|
|
|
|
review.Score.Should().BeLessThan(75);
|
|
review.Issues.Should().Contain(issue => issue.Message.Contains("Executive Summary"));
|
|
review.Issues.Should().Contain(issue => issue.Message.Contains("Recommendation"));
|
|
}
|
|
|
|
[Fact]
|
|
public void ReviewDeck_ShouldRewardBalancedConsultingDeck()
|
|
{
|
|
using var slides = JsonDocument.Parse(
|
|
"""
|
|
[
|
|
{ "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", "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"]] }
|
|
]
|
|
""");
|
|
|
|
var review = DeckQualityReviewService.ReviewDeck("Strong Deck", slides.RootElement, hasTemplate: true, autoRepairCount: 2, storyline: ["Executive Summary", "Options", "Roadmap"]);
|
|
|
|
review.Score.Should().BeGreaterThan(80);
|
|
review.Strengths.Should().Contain(strength => strength.Contains("Executive Summary"));
|
|
review.Issues.Should().NotContain(issue => issue.Severity == DeckReviewSeverity.Critical);
|
|
}
|
|
|
|
[Fact]
|
|
public void ReviewDeck_ShouldSurfaceSlideLevelQualityAlerts()
|
|
{
|
|
using var slides = JsonDocument.Parse(
|
|
"""
|
|
[
|
|
{ "layout": "title", "title": "Growth Strategy" },
|
|
{
|
|
"layout": "executive_summary",
|
|
"title": "Executive Summary",
|
|
"headline": "This headline is intentionally very long so that it exceeds the preferred consulting slide headline length and triggers the slide quality gate.",
|
|
"summary_points": ["Only one point"]
|
|
},
|
|
{
|
|
"layout": "comparison",
|
|
"title": "Options",
|
|
"headline": "Compare options",
|
|
"options": [{ "name": "A", "pros": "Fast", "cons": "Shallow", "verdict": "Only option" }]
|
|
}
|
|
]
|
|
""");
|
|
|
|
var review = DeckQualityReviewService.ReviewDeck("Alert Deck", slides.RootElement, hasTemplate: false, autoRepairCount: 0);
|
|
|
|
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"));
|
|
}
|
|
|
|
[Fact]
|
|
public void ReviewDeck_ShouldFlagMissingStorylineSlides_WhenHintsAreProvided()
|
|
{
|
|
using var slides = JsonDocument.Parse(
|
|
"""
|
|
[
|
|
{ "layout": "title", "title": "PMO Steering" },
|
|
{ "layout": "executive_summary", "title": "Executive Summary", "headline": "Message", "summary_points": ["A", "B"], "recommendation": "Proceed" },
|
|
{ "layout": "recommendation", "title": "Recommendation", "recommendation": "Proceed", "summary_points": ["Reason"] }
|
|
]
|
|
""");
|
|
|
|
var review = DeckQualityReviewService.ReviewDeck(
|
|
"Storyline Deck",
|
|
slides.RootElement,
|
|
hasTemplate: false,
|
|
autoRepairCount: 0,
|
|
storyline: ["Executive Summary", "Options", "Roadmap", "Appendix"]);
|
|
|
|
review.Issues.Should().Contain(issue => issue.Message.Contains("Storyline expects an options or comparison slide", StringComparison.OrdinalIgnoreCase));
|
|
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));
|
|
}
|
|
|
|
[Fact]
|
|
public void ReviewDeck_ShouldFlagComparisonTradeoffsAndKpiContext()
|
|
{
|
|
using var slides = JsonDocument.Parse(
|
|
"""
|
|
[
|
|
{ "layout": "title", "title": "Board Review" },
|
|
{
|
|
"layout": "executive_summary",
|
|
"title": "Executive Summary",
|
|
"summary_points": ["A", "B"],
|
|
"recommendation": "Proceed"
|
|
},
|
|
{
|
|
"layout": "comparison",
|
|
"title": "Options",
|
|
"headline": "Choose a path",
|
|
"options": [
|
|
{ "name": "A", "verdict": "Recommended" },
|
|
{ "name": "B", "verdict": "Fallback" }
|
|
]
|
|
},
|
|
{
|
|
"layout": "kpi_dashboard",
|
|
"title": "KPIs",
|
|
"headline": "Performance snapshot",
|
|
"kpis": [
|
|
{ "label": "Margin", "value": "+2.4pt" },
|
|
{ "label": "Lead Time", "value": "-12%" },
|
|
{ "label": "Quality", "value": "96%" }
|
|
]
|
|
}
|
|
]
|
|
""");
|
|
|
|
var review = DeckQualityReviewService.ReviewDeck("Thin Signals", slides.RootElement, hasTemplate: false, autoRepairCount: 0);
|
|
|
|
review.Issues.Should().Contain(issue => issue.Message.Contains("sharper single-message headline", StringComparison.OrdinalIgnoreCase));
|
|
review.Issues.Should().Contain(issue => issue.Message.Contains("trade-offs behind the verdict", StringComparison.OrdinalIgnoreCase));
|
|
review.Issues.Should().Contain(issue => issue.Message.Contains("trend or note context", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
}
|