SQL 정적 분석과 PPT·HTML 품질 기준을 강화하고 언어 fallback을 고도화한다
- SQL 전용 정적 분석 계층을 추가해 PostgreSQL/MySQL/SQL Server/SQLite/Oracle 방언 추정, statement kind 분류, object 추출, destructive DDL·broad DML·transaction boundary 위험 감지를 지원한다 - CodeLanguageCatalog의 SQL manifest/build/test/lint 힌트와 fallback summary를 SQL 분석 결과 중심으로 보강해 no-LSP 환경에서도 dialect·risk·next checks를 직접 안내한다 - DeckPlanningService가 구조화된 content 슬라이드를 kpi_dashboard/comparison/roadmap/chart로 자동 승격하고 DeckQualityReviewService·DeckRepairGuideService가 KPI 근거, verdict, owner/timeline, takeaway 부족을 별도 진단·보정한다 - HtmlSkill에 kpi_panel 섹션을 추가하고 ArtifactQualityReviewService·ArtifactRepairGuideService가 board/strategy 문서의 KPI·evidence·decision 연결 부족을 더 정확히 감지하도록 확장한다 - README.md, docs/DEVELOPMENT.md, docs/NEXT_ROADMAP.md에 2026-04-15 11:17 (KST) 기준 작업 이력과 검증 결과를 반영했다 검증 결과 - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_sql_doc_batch\ -p:IntermediateOutputPath=obj\verify_sql_doc_batch\ : 경고 0 / 오류 0 - dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter SqlDialectDetectorTests|SqlAnalysisServiceTests|CodeLanguageCatalogTests|DeckPlanningServiceTests|DeckQualityReviewServiceTests|ArtifactQualityReviewServiceTests|ArtifactRepairGuideServiceTests|HtmlSkillConsultingSectionsTests|HtmlSkillGoldenReportTests|PptxSkillGoldenDeckTests -p:OutputPath=bin\verify_sql_doc_batch_tests\ -p:IntermediateOutputPath=obj\verify_sql_doc_batch_tests\ : 통과 47
This commit is contained in:
@@ -130,6 +130,7 @@ public static class ArtifactQualityReviewService
|
||||
var roadmapCount = Regex.Matches(html, @"roadmap(-block|-phase)?", RegexOptions.IgnoreCase).Count;
|
||||
var matrixCount = Regex.Matches(html, @"matrix-grid|risk-matrix", RegexOptions.IgnoreCase).Count;
|
||||
var decisionCount = Regex.Matches(html, @"decision-summary", RegexOptions.IgnoreCase).Count;
|
||||
var kpiPanelCount = Regex.Matches(html, @"kpi-panel", RegexOptions.IgnoreCase).Count;
|
||||
var evidenceCardCount = Regex.Matches(html, @"evidence-cards|evidence-card", RegexOptions.IgnoreCase).Count;
|
||||
var boardPanelCount = Regex.Matches(html, @"board-report-panel", RegexOptions.IgnoreCase).Count;
|
||||
var strategyBriefCount = Regex.Matches(html, @"strategy-brief-panel", RegexOptions.IgnoreCase).Count;
|
||||
@@ -141,6 +142,7 @@ public static class ArtifactQualityReviewService
|
||||
+ (roadmapCount > 0 ? 1 : 0)
|
||||
+ (matrixCount > 0 ? 1 : 0)
|
||||
+ (decisionCount > 0 ? 1 : 0)
|
||||
+ (kpiPanelCount > 0 ? 1 : 0)
|
||||
+ (evidenceCardCount > 0 ? 1 : 0)
|
||||
+ (kpiCount > 0 ? 1 : 0);
|
||||
var supportingBlockEstimate = paragraphCount
|
||||
@@ -150,6 +152,7 @@ public static class ArtifactQualityReviewService
|
||||
+ roadmapCount
|
||||
+ matrixCount
|
||||
+ decisionCount
|
||||
+ kpiPanelCount
|
||||
+ evidenceCardCount
|
||||
+ boardPanelCount
|
||||
+ strategyBriefCount
|
||||
@@ -161,11 +164,12 @@ public static class ArtifactQualityReviewService
|
||||
if (printReady) strengths.Add("Includes print-ready CSS");
|
||||
if (hasPrintFrame) strengths.Add("Includes print header/footer frame");
|
||||
if (majorSectionEstimate >= 5) strengths.Add($"Contains {majorSectionEstimate} major sections");
|
||||
if (tableCount > 0 || comparisonCount > 0 || roadmapCount > 0 || matrixCount > 0 || decisionCount > 0 || evidenceCardCount > 0 || boardPanelCount > 0 || strategyBriefCount > 0 || kpiCount > 0)
|
||||
if (tableCount > 0 || comparisonCount > 0 || roadmapCount > 0 || matrixCount > 0 || decisionCount > 0 || kpiPanelCount > 0 || evidenceCardCount > 0 || boardPanelCount > 0 || strategyBriefCount > 0 || kpiCount > 0)
|
||||
strengths.Add("Uses structured business blocks");
|
||||
if (calloutCount > 0) strengths.Add("Uses callout blocks for emphasis");
|
||||
if (boardPanelCount > 0) strengths.Add("Includes board-ready summary panel");
|
||||
if (strategyBriefCount > 0) strengths.Add("Includes strategy brief panel");
|
||||
if (kpiPanelCount > 0) strengths.Add("Includes KPI panel");
|
||||
|
||||
if (html.Length < 1800)
|
||||
issues.Add(new("Body content may be too short for an executive-quality document.", ArtifactReviewSeverity.Warning));
|
||||
@@ -173,7 +177,7 @@ public static class ArtifactQualityReviewService
|
||||
issues.Add(new("Major section count is low for a business report.", ArtifactReviewSeverity.Warning));
|
||||
if (supportingBlockEstimate < Math.Max(4, majorSectionEstimate * 2))
|
||||
issues.Add(new("Several sections may need more supporting paragraphs.", ArtifactReviewSeverity.Warning));
|
||||
if (tableCount + comparisonCount + roadmapCount + matrixCount + decisionCount + evidenceCardCount + boardPanelCount + strategyBriefCount + kpiCount == 0)
|
||||
if (tableCount + comparisonCount + roadmapCount + matrixCount + decisionCount + kpiPanelCount + evidenceCardCount + boardPanelCount + strategyBriefCount + kpiCount == 0)
|
||||
issues.Add(new("Structured visual blocks are limited.", ArtifactReviewSeverity.Warning));
|
||||
if (placeholderCount > 0)
|
||||
issues.Add(new($"Found {placeholderCount} placeholder or unfinished marker(s).", ArtifactReviewSeverity.Critical));
|
||||
@@ -187,12 +191,18 @@ public static class ArtifactQualityReviewService
|
||||
issues.Add(new("Print-ready report could benefit from a cover page.", ArtifactReviewSeverity.Info));
|
||||
if (boardPanelCount > 0 && decisionCount == 0)
|
||||
issues.Add(new("Board-ready report should include a decision summary block.", ArtifactReviewSeverity.Warning));
|
||||
if (boardPanelCount > 0 && kpiPanelCount == 0 && kpiCount == 0)
|
||||
issues.Add(new("Board-ready report would be stronger with a KPI panel or metric strip.", ArtifactReviewSeverity.Info));
|
||||
if (boardPanelCount > 0 && evidenceCardCount == 0 && tableCount == 0)
|
||||
issues.Add(new("Board-ready report would benefit from evidence cards or a supporting table.", ArtifactReviewSeverity.Info));
|
||||
if (strategyBriefCount > 0 && decisionCount == 0)
|
||||
issues.Add(new("Strategy brief should include explicit decisions or a decision summary block.", ArtifactReviewSeverity.Warning));
|
||||
if (strategyBriefCount > 0 && comparisonCount + roadmapCount == 0)
|
||||
issues.Add(new("Strategy brief would be stronger with a comparison or roadmap block.", ArtifactReviewSeverity.Info));
|
||||
if (strategyBriefCount > 0 && evidenceCardCount + kpiPanelCount == 0)
|
||||
issues.Add(new("Strategy brief would benefit from KPI or evidence support blocks.", ArtifactReviewSeverity.Info));
|
||||
if (kpiPanelCount > 0 && decisionCount == 0 && boardPanelCount == 0)
|
||||
issues.Add(new("KPI panel should roll into an explicit decision or next-step block.", ArtifactReviewSeverity.Info));
|
||||
|
||||
return BuildReport("html", strengths, issues);
|
||||
}
|
||||
|
||||
@@ -36,16 +36,22 @@ public static class ArtifactRepairGuideService
|
||||
{
|
||||
if (message.Contains("Board-ready report should include a decision summary block", StringComparison.OrdinalIgnoreCase))
|
||||
return "Add a decision summary block directly after the board ask so the approval point is explicit";
|
||||
if (message.Contains("KPI panel or metric strip", StringComparison.OrdinalIgnoreCase))
|
||||
return "Add a KPI panel or metric strip so the board report shows the top-line business signal at a glance";
|
||||
if (message.Contains("decision summary or evidence cards", StringComparison.OrdinalIgnoreCase))
|
||||
return "Add a decision summary and evidence cards near the recommendation section";
|
||||
if (message.Contains("Strategy brief should include explicit decisions", StringComparison.OrdinalIgnoreCase))
|
||||
return "Add an explicit decisions block or decision summary to the strategy brief";
|
||||
if (message.Contains("KPI or evidence support blocks", StringComparison.OrdinalIgnoreCase))
|
||||
return "Add KPI or evidence support blocks so the strategy brief is backed by measurable proof";
|
||||
if (message.Contains("cover page", StringComparison.OrdinalIgnoreCase))
|
||||
return "Add a cover page for print-ready or board-facing reports";
|
||||
if (message.Contains("table of contents", StringComparison.OrdinalIgnoreCase))
|
||||
return "Add a table of contents for longer print-ready reports";
|
||||
if (message.Contains("comparison or roadmap", StringComparison.OrdinalIgnoreCase))
|
||||
return "Add comparison or roadmap blocks to make options and sequencing explicit";
|
||||
if (message.Contains("explicit decision or next-step block", StringComparison.OrdinalIgnoreCase))
|
||||
return "Connect the KPI panel to an explicit decision or next-step block";
|
||||
if (message.Contains("supporting paragraphs", StringComparison.OrdinalIgnoreCase))
|
||||
return "Expand the core sections with supporting paragraphs and business evidence";
|
||||
if (message.Contains("Structured visual blocks are limited", StringComparison.OrdinalIgnoreCase))
|
||||
|
||||
@@ -163,6 +163,35 @@ public static class DeckPlanningService
|
||||
repairs++;
|
||||
}
|
||||
|
||||
var requestedLayout = layout.Trim().ToLowerInvariant();
|
||||
if (requestedLayout == "content")
|
||||
{
|
||||
if (HasArrayItems(slide, "kpis"))
|
||||
{
|
||||
slide["layout"] = "kpi_dashboard";
|
||||
layout = "kpi_dashboard";
|
||||
repairs++;
|
||||
}
|
||||
else if (HasChartData(slide))
|
||||
{
|
||||
slide["layout"] = "chart";
|
||||
layout = "chart";
|
||||
repairs++;
|
||||
}
|
||||
else if (HasArrayItems(slide, "options"))
|
||||
{
|
||||
slide["layout"] = "comparison";
|
||||
layout = "comparison";
|
||||
repairs++;
|
||||
}
|
||||
else if (HasArrayItems(slide, "phases"))
|
||||
{
|
||||
slide["layout"] = "roadmap";
|
||||
layout = "roadmap";
|
||||
repairs++;
|
||||
}
|
||||
}
|
||||
|
||||
switch (layout.Trim().ToLowerInvariant())
|
||||
{
|
||||
case "issue_tree":
|
||||
@@ -599,6 +628,13 @@ public static class DeckPlanningService
|
||||
values.Count > 0;
|
||||
}
|
||||
|
||||
private static bool HasArrayItems(JsonObject slide, string propertyName)
|
||||
{
|
||||
return slide.TryGetPropertyValue(propertyName, out var node)
|
||||
&& node is JsonArray array
|
||||
&& array.Count > 0;
|
||||
}
|
||||
|
||||
private static int ClampArray(JsonObject slide, string propertyName, int maxCount)
|
||||
{
|
||||
if (!slide.TryGetPropertyValue(propertyName, out var node) || node is not JsonArray array || array.Count <= maxCount)
|
||||
|
||||
@@ -212,6 +212,11 @@ public static class DeckQualityReviewService
|
||||
issues.Add(new($"Slide {slideNumber}: Executive Summary should include a recommendation or decision ask.", DeckReviewSeverity.Info));
|
||||
added++;
|
||||
}
|
||||
if (ReadJsonArrayCount(slide, "kpis") == 0)
|
||||
{
|
||||
issues.Add(new($"Slide {slideNumber}: Executive Summary should include KPI cards or quantified evidence.", DeckReviewSeverity.Info));
|
||||
added++;
|
||||
}
|
||||
break;
|
||||
case "recommendation":
|
||||
if (string.IsNullOrWhiteSpace(ReadString(slide, "recommendation")))
|
||||
@@ -232,6 +237,11 @@ public static class DeckQualityReviewService
|
||||
issues.Add(new($"Slide {slideNumber}: comparison slide needs at least two options.", DeckReviewSeverity.Warning));
|
||||
added++;
|
||||
}
|
||||
else if (!slide.GetRawText().Contains("\"verdict\"", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
issues.Add(new($"Slide {slideNumber}: comparison slide should highlight a clear verdict.", DeckReviewSeverity.Info));
|
||||
added++;
|
||||
}
|
||||
break;
|
||||
case "roadmap":
|
||||
if (ReadJsonArrayCount(slide, "phases") == 0)
|
||||
@@ -244,6 +254,12 @@ public static class DeckQualityReviewService
|
||||
issues.Add(new($"Slide {slideNumber}: roadmap slide should show at least two phases.", DeckReviewSeverity.Info));
|
||||
added++;
|
||||
}
|
||||
if (!slide.GetRawText().Contains("\"owner\"", StringComparison.OrdinalIgnoreCase) ||
|
||||
!slide.GetRawText().Contains("\"timeline\"", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
issues.Add(new($"Slide {slideNumber}: roadmap slide would be stronger with timeline and owner labels.", DeckReviewSeverity.Info));
|
||||
added++;
|
||||
}
|
||||
break;
|
||||
case "chart":
|
||||
if (ReadJsonArrayCount(slide, "chart_labels") == 0 || ReadJsonArrayCount(slide, "chart_values") == 0)
|
||||
@@ -259,6 +275,19 @@ public static class DeckQualityReviewService
|
||||
added++;
|
||||
}
|
||||
break;
|
||||
case "kpi_dashboard":
|
||||
if (ReadJsonArrayCount(slide, "kpis") < 3)
|
||||
{
|
||||
issues.Add(new($"Slide {slideNumber}: KPI dashboard should show at least three metrics.", DeckReviewSeverity.Warning));
|
||||
added++;
|
||||
}
|
||||
if (ReadStringList(slide, "summary_points").Count == 0 &&
|
||||
ReadStringList(slide, "body").Count == 0)
|
||||
{
|
||||
issues.Add(new($"Slide {slideNumber}: KPI dashboard needs takeaway points, not just metric tiles.", DeckReviewSeverity.Info));
|
||||
added++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return added;
|
||||
|
||||
@@ -16,12 +16,18 @@ public static class DeckRepairGuideService
|
||||
=> "Add a recommendation or decision ask directly to the Executive Summary",
|
||||
var message when message.Contains("Executive Summary", StringComparison.OrdinalIgnoreCase)
|
||||
=> "Add or strengthen the Executive Summary with 2-3 evidence-backed takeaways",
|
||||
var message when message.Contains("KPI cards or quantified evidence", StringComparison.OrdinalIgnoreCase)
|
||||
=> "Add KPI cards or quantified evidence so the executive message is backed by numbers",
|
||||
var message when message.Contains("supporting rationale or next steps", StringComparison.OrdinalIgnoreCase)
|
||||
=> "Add supporting rationale or next steps so the recommendation turns into action",
|
||||
var message when message.Contains("Recommendation", StringComparison.OrdinalIgnoreCase)
|
||||
=> "Add a clear recommendation or decision request slide near the end of the deck",
|
||||
var message when message.Contains("roadmap", StringComparison.OrdinalIgnoreCase)
|
||||
=> "Add a roadmap slide with phases, owners, and timing",
|
||||
var message when message.Contains("clear verdict", StringComparison.OrdinalIgnoreCase)
|
||||
=> "Label the preferred option explicitly so the comparison slide lands a decision",
|
||||
var message when message.Contains("timeline and owner labels", StringComparison.OrdinalIgnoreCase)
|
||||
=> "Add timeline and owner labels so the roadmap reads like an execution plan",
|
||||
var message when message.Contains("Appendix or evidence slide", StringComparison.OrdinalIgnoreCase)
|
||||
=> "Add appendix or evidence slides with supporting data, assumptions, or source tables",
|
||||
var message when message.Contains("duplicate headline", StringComparison.OrdinalIgnoreCase)
|
||||
@@ -42,6 +48,10 @@ public static class DeckRepairGuideService
|
||||
=> "Provide chart labels and values or replace the slide with a comparison/evidence layout",
|
||||
var message when message.Contains("table slide", StringComparison.OrdinalIgnoreCase)
|
||||
=> "Provide complete table headers and rows or simplify the slide to key callouts",
|
||||
var message when message.Contains("KPI dashboard should show at least three metrics", StringComparison.OrdinalIgnoreCase)
|
||||
=> "Expand the KPI dashboard to at least three core metrics so the trend is clear",
|
||||
var message when message.Contains("KPI dashboard needs takeaway points", StringComparison.OrdinalIgnoreCase)
|
||||
=> "Add takeaway points beneath the KPI tiles so the metrics translate into action",
|
||||
var message when message.Contains("text-heavy", StringComparison.OrdinalIgnoreCase)
|
||||
=> "Convert text-heavy slides into message-led visuals with fewer bullets",
|
||||
var message when message.Contains("Evidence slides", StringComparison.OrdinalIgnoreCase)
|
||||
|
||||
@@ -53,6 +53,7 @@ public class HtmlSkill : IAgentTool
|
||||
"'chart' {kind:'bar|horizontal_bar', title, data:[{label, value, color}]}, " +
|
||||
"'cards' {items:[{title, body, badge, icon}]}, " +
|
||||
"'decision_summary' {title, decision, rationale, actions:['...']}, " +
|
||||
"'kpi_panel' {title, headline, items:[{label,value,trend,note}], takeaway}, " +
|
||||
"'evidence_cards' {title, items:[{title, detail, source, tag}]}, " +
|
||||
"'board_report' {title, decision, recommendation, rationale, metrics:[{label,value,note}], risks:['...'], next_steps:['...']}, " +
|
||||
"'strategy_brief' {title, strategic_question, thesis, implications:['...'], decisions:['...']}, " +
|
||||
@@ -357,6 +358,9 @@ public class HtmlSkill : IAgentTool
|
||||
case "decision_summary":
|
||||
sb.AppendLine(RenderDecisionSummary(section));
|
||||
break;
|
||||
case "kpi_panel":
|
||||
sb.AppendLine(RenderKpiPanel(section));
|
||||
break;
|
||||
case "evidence_cards":
|
||||
sb.AppendLine(RenderEvidenceCards(section));
|
||||
break;
|
||||
@@ -605,6 +609,24 @@ public class HtmlSkill : IAgentTool
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string RenderKpiPanel(JsonElement s)
|
||||
{
|
||||
var title = s.SafeTryGetProperty("title", out var titleEl) ? titleEl.SafeGetString() : "KPI Panel";
|
||||
var headline = s.SafeTryGetProperty("headline", out var headlineEl) ? headlineEl.SafeGetString() : null;
|
||||
var takeaway = s.SafeTryGetProperty("takeaway", out var takeawayEl) ? takeawayEl.SafeGetString() : null;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("<section class=\"kpi-panel\" style=\"margin:1.4rem 0;padding:1.15rem 1.2rem;border:1px solid #dbe4f0;border-radius:18px;background:linear-gradient(180deg,#f8fafc,#ffffff);box-shadow:0 10px 26px rgba(15,23,42,.05);\">");
|
||||
sb.AppendLine($"<div style=\"font-size:1rem;font-weight:800;color:#0f172a;margin-bottom:.55rem\">{Escape(title ?? "KPI Panel")}</div>");
|
||||
if (!string.IsNullOrWhiteSpace(headline))
|
||||
sb.AppendLine($"<div style=\"font-size:.95rem;font-weight:700;color:#1d4ed8;margin-bottom:.85rem\">{MarkdownToHtml(headline)}</div>");
|
||||
sb.AppendLine(RenderKpi(s));
|
||||
if (!string.IsNullOrWhiteSpace(takeaway))
|
||||
sb.AppendLine($"<div style=\"margin-top:.85rem;padding:.8rem 1rem;border-radius:14px;background:#eef2ff;color:#312e81;font-weight:700\">Takeaway: {MarkdownToHtml(takeaway)}</div>");
|
||||
sb.AppendLine("</section>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string RenderEvidenceCards(JsonElement s)
|
||||
{
|
||||
if (!s.SafeTryGetProperty("items", out var items) || items.ValueKind != JsonValueKind.Array)
|
||||
|
||||
Reference in New Issue
Block a user