HTML archetype·Excel dashboard·PPT 골든 회귀를 추가 고도화
- HtmlSkill에 board_report와 strategy_brief 구조화 섹션 타입을 추가해 이사회 보고형/전략 요약형 HTML 산출물 표현력을 확장 - ArtifactQualityReviewService HTML 리뷰에 board-report-panel, strategy-brief-panel 인식과 보완 포인트 규칙을 추가 - Excel dashboard sheet에 KPI, highlights, actions를 함께 렌더링해 executive dashboard 시트 밀도를 강화 - PptxSkillGoldenDeckTests에 strategy deck 회귀 샘플을 추가해 strong 전략 덱 품질 기준을 고정 - README.md와 docs/DEVELOPMENT.md에 2026-04-14 23:32 (KST) 기준 이력과 검증 명령을 반영 검증 결과 - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_next5\\ -p:IntermediateOutputPath=obj\\verify_doc_next5\\ : 경고 0 / 오류 0 - dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter ArtifactQualityReviewServiceTests|ExcelSkillDashboardSummaryTests|HtmlSkillConsultingSectionsTests|HtmlSkillPrintFrameTests|DeckQualityReviewServiceTests|PptxSkillGoldenDeckTests|PptxSkillAutoRepairTests|PptxSkillConsultingDeckTests -p:OutputPath=bin\\verify_doc_next5_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_next5_tests\\ : 통과 14
This commit is contained in:
@@ -1847,3 +1847,12 @@ MIT License
|
||||
- [PptxSkillGoldenDeckTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/PptxSkillGoldenDeckTests.cs)를 추가해 strong board deck이 `PPT quality` 요약을 안정적으로 반환하고 불필요한 `Slide alerts` 없이 통과하는지 golden 회귀로 고정했습니다.
|
||||
- 검증: `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
|
||||
|
||||
업데이트: 2026-04-14 23:32 (KST)
|
||||
- HTML archetype을 한 단계 더 올렸습니다. [HtmlSkill.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/HtmlSkill.cs)는 `board_report`, `strategy_brief` 구조화 섹션 타입을 새로 지원합니다. 이 섹션들은 의사결정 요청, recommendation, rationale, KPI/metric cards, risks, next steps, strategic question, thesis, implications, decisions를 목적형 패널로 렌더링해 board report와 strategy brief를 더 빠르게 만들 수 있습니다.
|
||||
- [ArtifactQualityReviewService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ArtifactQualityReviewService.cs)의 HTML 리뷰는 이제 `board-report-panel`, `strategy-brief-panel`을 강점으로 인식하고, board report인데 evidence/table이 부족하거나 strategy brief인데 comparison/roadmap이 없을 때 보완 포인트를 반환합니다.
|
||||
- [ExcelSkill.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ExcelSkill.cs)의 `Dashboard` 시트는 decision summary, scorecards, trend dashboard뿐 아니라 `kpis`, `highlights`, `actions`까지 함께 담도록 보강했습니다. summary sheet와 dashboard sheet가 서로 다른 역할을 갖도록 executive dashboard 밀도를 높인 변경입니다.
|
||||
- [PptxSkillGoldenDeckTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/PptxSkillGoldenDeckTests.cs)는 board deck에 이어 strategy deck golden fixture를 추가했습니다. strong strategy deck도 `PPT quality` 요약을 반환하고 불필요한 `Slide alerts` 없이 통과하는지를 회귀 기준으로 고정했습니다.
|
||||
- 테스트로 [HtmlSkillConsultingSectionsTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/HtmlSkillConsultingSectionsTests.cs), [ExcelSkillDashboardSummaryTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ExcelSkillDashboardSummaryTests.cs), [ArtifactQualityReviewServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ArtifactQualityReviewServiceTests.cs), [PptxSkillGoldenDeckTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/PptxSkillGoldenDeckTests.cs)를 확장했습니다.
|
||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_next5\\ -p:IntermediateOutputPath=obj\\verify_doc_next5\\` 경고 0 / 오류 0
|
||||
- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ArtifactQualityReviewServiceTests|ExcelSkillDashboardSummaryTests|HtmlSkillConsultingSectionsTests|HtmlSkillPrintFrameTests|DeckQualityReviewServiceTests|PptxSkillGoldenDeckTests|PptxSkillAutoRepairTests|PptxSkillConsultingDeckTests" -p:OutputPath=bin\\verify_doc_next5_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_next5_tests\\` 통과 14
|
||||
|
||||
@@ -920,3 +920,12 @@ UI ?붿옄???洹쒕え 由ы뙥?좊쭅 ???꾪뿕 ?묒뾽 ??湲곕줉???덉쟾
|
||||
- [PptxSkillGoldenDeckTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/PptxSkillGoldenDeckTests.cs)를 추가해 strong board deck이 `PPT quality` 요약을 안정적으로 반환하고 불필요한 `Slide alerts` 없이 통과하는지 golden regression으로 고정했습니다.
|
||||
- 검증: `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
|
||||
|
||||
업데이트: 2026-04-14 23:32 (KST)
|
||||
- [HtmlSkill.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/HtmlSkill.cs)는 `board_report`, `strategy_brief` 구조화 섹션 타입을 추가했습니다. board report는 decision ask, recommendation, rationale, metrics, risks, next steps를 board-ready 패널로 렌더링하고, strategy brief는 strategic question, core thesis, implications, decisions를 전략 요약 패널로 렌더링합니다.
|
||||
- [ArtifactQualityReviewService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ArtifactQualityReviewService.cs)의 HTML 리뷰는 `board-report-panel`, `strategy-brief-panel`을 새 강점으로 인식합니다. 반대로 board report인데 evidence/table이 없거나 strategy brief인데 comparison/roadmap이 없을 때는 추가 보완 포인트를 반환하도록 규칙을 보강했습니다.
|
||||
- [ExcelSkill.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ExcelSkill.cs)의 `WriteDashboardSheet()`는 dashboard sheet에 `kpis`, `highlights`, `actions`까지 함께 표시하도록 확장됐습니다. summary sheet와 dashboard sheet가 서로 같은 정보를 단순 중복하는 대신, executive dashboard 성격을 더 분명히 갖도록 정리한 변경입니다.
|
||||
- [PptxSkillGoldenDeckTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/PptxSkillGoldenDeckTests.cs)는 board deck 외에 strategy deck golden fixture를 추가했습니다. `storyline`, `decision_ask`, recommendation headline 길이까지 품질 게이트 기준에 맞는 strong strategy deck을 회귀 샘플로 고정했습니다.
|
||||
- 테스트로 [HtmlSkillConsultingSectionsTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/HtmlSkillConsultingSectionsTests.cs), [ExcelSkillDashboardSummaryTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ExcelSkillDashboardSummaryTests.cs), [ArtifactQualityReviewServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ArtifactQualityReviewServiceTests.cs), [PptxSkillGoldenDeckTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/PptxSkillGoldenDeckTests.cs)를 확장했습니다.
|
||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_next5\\ -p:IntermediateOutputPath=obj\\verify_doc_next5\\` 경고 0 / 오류 0
|
||||
- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ArtifactQualityReviewServiceTests|ExcelSkillDashboardSummaryTests|HtmlSkillConsultingSectionsTests|HtmlSkillPrintFrameTests|DeckQualityReviewServiceTests|PptxSkillGoldenDeckTests|PptxSkillAutoRepairTests|PptxSkillConsultingDeckTests" -p:OutputPath=bin\\verify_doc_next5_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_next5_tests\\` 통과 14
|
||||
|
||||
@@ -17,6 +17,8 @@ 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>
|
||||
<section class="board-report-panel"></section>
|
||||
<section class="strategy-brief-panel"></section>
|
||||
<h2>Recommendation</h2><p>Recommendation text.</p><p>Action support text.</p>
|
||||
<h2>Appendix</h2><p>Reference detail.</p><p>More detail.</p>
|
||||
""";
|
||||
@@ -27,6 +29,7 @@ public class ArtifactQualityReviewServiceTests
|
||||
review.Strengths.Should().Contain(strength =>
|
||||
strength.Contains("cover", StringComparison.OrdinalIgnoreCase) ||
|
||||
strength.Contains("contents", StringComparison.OrdinalIgnoreCase));
|
||||
review.Strengths.Should().Contain(strength => strength.Contains("board-ready", StringComparison.OrdinalIgnoreCase));
|
||||
review.Issues.Should().NotContain(issue => issue.Severity == ArtifactReviewSeverity.Critical);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,9 @@ public class ExcelSkillDashboardSummaryTests
|
||||
"scorecards": [
|
||||
{ "label": "Run-rate", "value": "96%", "status": "On Track", "note": "Ahead of plan" }
|
||||
],
|
||||
"kpis": [
|
||||
{ "label": "Margin", "value": "18%", "trend": "Q/Q", "note": "Recovered" }
|
||||
],
|
||||
"trend_series": [
|
||||
{ "label": "Revenue", "current": "120", "target": "130", "delta": "-10", "status": "Watch" }
|
||||
],
|
||||
@@ -94,6 +97,8 @@ public class ExcelSkillDashboardSummaryTests
|
||||
dashboardTexts.Should().Contain("Operating Review Dashboard");
|
||||
dashboardTexts.Should().Contain("Linked Detail Sheets");
|
||||
dashboardTexts.Should().Contain("Open: Revenue");
|
||||
dashboardTexts.Should().Contain("Key KPIs");
|
||||
dashboardTexts.Should().Contain("Margin");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -67,6 +67,26 @@ public class HtmlSkillConsultingSectionsTests
|
||||
{ "title": "Margin uplift", "detail": "+4.2p improvement after workflow change", "source": "Finance close", "tag": "KPI" },
|
||||
{ "title": "Cycle time", "detail": "Lead time reduced from 12 to 8 days", "source": "PMO tracker", "tag": "Ops" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "board_report",
|
||||
"title": "Board Ask",
|
||||
"decision": "Approve the next rollout wave",
|
||||
"recommendation": "Release the phase-2 budget now",
|
||||
"rationale": "The pilot hit margin and lead-time targets.",
|
||||
"metrics": [
|
||||
{ "label": "Margin", "value": "+4.2p", "note": "pilot" }
|
||||
],
|
||||
"risks": ["Adoption lag in region B"],
|
||||
"next_steps": ["Confirm budget", "Launch phase 2"]
|
||||
},
|
||||
{
|
||||
"type": "strategy_brief",
|
||||
"title": "Strategy Brief",
|
||||
"strategic_question": "Where should we scale first?",
|
||||
"thesis": "Scale the high-retention segment first.",
|
||||
"implications": ["Refocus sales coverage", "Prioritize onboarding"],
|
||||
"decisions": ["Approve segment focus", "Adjust regional targets"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -81,6 +101,8 @@ public class HtmlSkillConsultingSectionsTests
|
||||
html.Should().Contain("matrix-grid");
|
||||
html.Should().Contain("decision-summary");
|
||||
html.Should().Contain("evidence-cards");
|
||||
html.Should().Contain("board-report-panel");
|
||||
html.Should().Contain("strategy-brief-panel");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -121,4 +121,120 @@ public class PptxSkillGoldenDeckTests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithStrongStrategyDeck_ShouldRemainFreeOfSlideAlerts()
|
||||
{
|
||||
var workDir = Path.Combine(Path.GetTempPath(), "ax-pptx-golden-strategy-" + 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": "strategy-golden.pptx",
|
||||
"theme": "professional",
|
||||
"audience": "CEO staff",
|
||||
"objective": "Confirm the next-phase growth strategy",
|
||||
"decision_ask": "Approve the SMB-first expansion plan",
|
||||
"storyline": ["Executive Summary", "Current State", "Options", "Roadmap", "Recommendation", "Appendix"],
|
||||
"slides": [
|
||||
{
|
||||
"layout": "title",
|
||||
"title": "Growth Strategy Refresh"
|
||||
},
|
||||
{
|
||||
"layout": "executive_summary",
|
||||
"title": "Executive Summary",
|
||||
"headline": "An SMB-first strategy gives the best balance of growth, margin, and execution risk.",
|
||||
"summary_points": [
|
||||
"The SMB segment now drives the strongest retention and the fastest sales cycle.",
|
||||
"Enterprise expansion remains attractive but requires heavier onboarding capacity.",
|
||||
"The SMB-first path improves near-term growth while keeping operating complexity in bounds."
|
||||
],
|
||||
"recommendation": "Prioritize SMB expansion, stage enterprise enablement, and re-sequence hiring.",
|
||||
"kpis": [
|
||||
{ "label": "Growth", "value": "+14%", "trend": "run-rate", "note": "SMB mix" },
|
||||
{ "label": "Margin", "value": "+1.8pt", "trend": "plan", "note": "lower service load" },
|
||||
{ "label": "Retention", "value": "92%", "trend": "SMB", "note": "stable" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"layout": "comparison",
|
||||
"title": "Strategic Options",
|
||||
"headline": "SMB-first is the strongest near-term path while preserving the enterprise option.",
|
||||
"options": [
|
||||
{ "name": "SMB first", "pros": "Fast cycle and strong retention", "cons": "Smaller deal size", "verdict": "Recommended" },
|
||||
{ "name": "Balanced mix", "pros": "Diversified growth", "cons": "Higher delivery complexity", "verdict": "Balanced" },
|
||||
{ "name": "Enterprise first", "pros": "Largest upside", "cons": "Slowest ramp and highest risk", "verdict": "Long-term only" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"layout": "roadmap",
|
||||
"title": "Execution Roadmap",
|
||||
"headline": "Deliver in three phases with clear commercial and delivery checkpoints.",
|
||||
"phases": [
|
||||
{ "title": "Focus", "detail": "Reset segment priorities and targets", "timeline": "Weeks 1-2", "owner": "Strategy" },
|
||||
{ "title": "Enable", "detail": "Align pricing, messaging, and onboarding", "timeline": "Weeks 3-6", "owner": "Sales + Ops" },
|
||||
{ "title": "Scale", "detail": "Expand channel coverage and monitor retention", "timeline": "Weeks 7-12", "owner": "GM" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"layout": "recommendation",
|
||||
"title": "Recommendation",
|
||||
"headline": "Approve the SMB-first plan and stage enterprise investment after week 8.",
|
||||
"recommendation": "Approve the SMB-first growth plan and stage enterprise investments after onboarding capacity stabilizes.",
|
||||
"summary_points": [
|
||||
"This path captures the highest near-term growth with the cleanest operating model.",
|
||||
"It keeps the enterprise option open without overloading delivery teams.",
|
||||
"The required decision is sequencing, not strategic direction."
|
||||
],
|
||||
"next_steps": [
|
||||
"Reset next-quarter targets",
|
||||
"Lock SMB enablement backlog",
|
||||
"Review enterprise readiness after week 8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"layout": "table",
|
||||
"title": "Appendix & Evidence",
|
||||
"headers": ["Observation", "Implication"],
|
||||
"rows": [
|
||||
["Retention pattern", "SMB cohort retention is consistently above plan."],
|
||||
["Onboarding load", "Enterprise onboarding is the main delivery constraint."]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
File.Exists(Path.Combine(workDir, "strategy-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
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,8 @@ public static class ArtifactQualityReviewService
|
||||
var matrixCount = Regex.Matches(html, @"matrix-grid|risk-matrix", RegexOptions.IgnoreCase).Count;
|
||||
var decisionCount = Regex.Matches(html, @"decision-summary", 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;
|
||||
var kpiCount = Regex.Matches(html, @"kpi-card|kpi-grid|metric-card", RegexOptions.IgnoreCase).Count;
|
||||
var placeholderCount = CountPlaceholders(html);
|
||||
|
||||
@@ -136,9 +138,11 @@ public static class ArtifactQualityReviewService
|
||||
if (printReady) strengths.Add("Includes print-ready CSS");
|
||||
if (hasPrintFrame) strengths.Add("Includes print header/footer frame");
|
||||
if (sectionCount >= 5) strengths.Add($"Contains {sectionCount} major sections");
|
||||
if (tableCount > 0 || comparisonCount > 0 || roadmapCount > 0 || matrixCount > 0 || decisionCount > 0 || evidenceCardCount > 0 || kpiCount > 0)
|
||||
if (tableCount > 0 || comparisonCount > 0 || roadmapCount > 0 || matrixCount > 0 || decisionCount > 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 (html.Length < 1800)
|
||||
issues.Add(new("Body content may be too short for an executive-quality document.", ArtifactReviewSeverity.Warning));
|
||||
@@ -146,7 +150,7 @@ public static class ArtifactQualityReviewService
|
||||
issues.Add(new("Major section count is low for a business report.", ArtifactReviewSeverity.Warning));
|
||||
if (paragraphCount < sectionCount * 2)
|
||||
issues.Add(new("Several sections may need more supporting paragraphs.", ArtifactReviewSeverity.Warning));
|
||||
if (tableCount + comparisonCount + roadmapCount + matrixCount + decisionCount + evidenceCardCount + kpiCount == 0)
|
||||
if (tableCount + comparisonCount + roadmapCount + matrixCount + decisionCount + 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));
|
||||
@@ -158,6 +162,10 @@ public static class ArtifactQualityReviewService
|
||||
issues.Add(new("Print-ready business report would benefit from decision summary or evidence cards.", ArtifactReviewSeverity.Warning));
|
||||
if (printReady && !hasCover && sectionCount >= 5)
|
||||
issues.Add(new("Print-ready report could benefit from a cover page.", 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 && comparisonCount + roadmapCount == 0)
|
||||
issues.Add(new("Strategy brief would be stronger with a comparison or roadmap block.", ArtifactReviewSeverity.Info));
|
||||
|
||||
return BuildReport("html", strengths, issues);
|
||||
}
|
||||
|
||||
@@ -618,7 +618,9 @@ public class ExcelSkill : IAgentTool
|
||||
AppendDecisionSummarySection(summarySheet, sheetData, merges, ref rowIndex);
|
||||
AppendScorecardSection(summarySheet, sheetData, ref rowIndex);
|
||||
AppendTrendSection(summarySheet, sheetData, ref rowIndex);
|
||||
AppendKpiSection(summarySheet, sheetData, merges, ref rowIndex);
|
||||
AppendSheetSummarySection(summarySheet, sheetData, ref rowIndex);
|
||||
AppendSummaryTextSection(summarySheet, "highlights", "Key Highlights", sheetData, merges, ref rowIndex);
|
||||
AppendSummaryTextSection(summarySheet, "actions", "Priority Actions", sheetData, merges, ref rowIndex);
|
||||
|
||||
if (detailSheetNames.Count > 0)
|
||||
@@ -789,6 +791,35 @@ public class ExcelSkill : IAgentTool
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
private static void AppendKpiSection(JsonElement summarySheet, SheetData sheetData, MergeCells merges, ref uint rowIndex)
|
||||
{
|
||||
if (!summarySheet.SafeTryGetProperty("kpis", out var kpis) || kpis.ValueKind != JsonValueKind.Array || kpis.GetArrayLength() == 0)
|
||||
return;
|
||||
|
||||
AppendMergedTextRow(sheetData, merges, rowIndex++, "A", "F", "Key KPIs", 1);
|
||||
var headerRow = new Row { RowIndex = rowIndex++ };
|
||||
headerRow.Append(CreateSummaryCell("A", headerRow.RowIndex!.Value, "Metric", 1));
|
||||
headerRow.Append(CreateSummaryCell("B", headerRow.RowIndex!.Value, "Value", 1));
|
||||
headerRow.Append(CreateSummaryCell("C", headerRow.RowIndex!.Value, "Trend", 1));
|
||||
headerRow.Append(CreateSummaryCell("D", headerRow.RowIndex!.Value, "Note", 1));
|
||||
sheetData.Append(headerRow);
|
||||
|
||||
var index = 0;
|
||||
foreach (var kpi in kpis.EnumerateArray())
|
||||
{
|
||||
var stripeStyle = index % 2 == 0 ? (uint)0 : (uint)2;
|
||||
var row = new Row { RowIndex = rowIndex++ };
|
||||
row.Append(CreateSummaryCell("A", row.RowIndex!.Value, kpi.SafeTryGetProperty("label", out var labelEl) ? labelEl.SafeGetString() ?? string.Empty : string.Empty, 1));
|
||||
row.Append(CreateSummaryCell("B", row.RowIndex!.Value, kpi.SafeTryGetProperty("value", out var valueEl) ? valueEl.SafeGetString() ?? string.Empty : string.Empty, 3));
|
||||
row.Append(CreateSummaryCell("C", row.RowIndex!.Value, kpi.SafeTryGetProperty("trend", out var trendEl) ? trendEl.SafeGetString() ?? string.Empty : string.Empty, stripeStyle));
|
||||
row.Append(CreateSummaryCell("D", row.RowIndex!.Value, kpi.SafeTryGetProperty("note", out var noteEl) ? noteEl.SafeGetString() ?? string.Empty : string.Empty, stripeStyle));
|
||||
sheetData.Append(row);
|
||||
index++;
|
||||
}
|
||||
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
private static bool HasDashboardSheet(JsonElement summarySheet)
|
||||
{
|
||||
if (summarySheet.ValueKind != JsonValueKind.Object)
|
||||
|
||||
@@ -54,6 +54,8 @@ public class HtmlSkill : IAgentTool
|
||||
"'cards' {items:[{title, body, badge, icon}]}, " +
|
||||
"'decision_summary' {title, decision, rationale, actions:['...']}, " +
|
||||
"'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:['...']}, " +
|
||||
"'comparison' {title, items:[{name, summary, pros, cons, verdict}]}, " +
|
||||
"'roadmap' {title, phases:[{title, detail, timeline, owner}]}, " +
|
||||
"'matrix' {title, quadrants:[{title, items:['...']}]}, " +
|
||||
@@ -357,6 +359,12 @@ public class HtmlSkill : IAgentTool
|
||||
case "evidence_cards":
|
||||
sb.AppendLine(RenderEvidenceCards(section));
|
||||
break;
|
||||
case "board_report":
|
||||
sb.AppendLine(RenderBoardReport(section));
|
||||
break;
|
||||
case "strategy_brief":
|
||||
sb.AppendLine(RenderStrategyBrief(section));
|
||||
break;
|
||||
case "comparison":
|
||||
sb.AppendLine(RenderComparison(section));
|
||||
break;
|
||||
@@ -630,6 +638,111 @@ public class HtmlSkill : IAgentTool
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string RenderBoardReport(JsonElement s)
|
||||
{
|
||||
var title = s.SafeTryGetProperty("title", out var titleEl) ? titleEl.SafeGetString() : "Board Report";
|
||||
var decision = s.SafeTryGetProperty("decision", out var decisionEl) ? decisionEl.SafeGetString() : null;
|
||||
var recommendation = s.SafeTryGetProperty("recommendation", out var recommendationEl) ? recommendationEl.SafeGetString() : null;
|
||||
var rationale = s.SafeTryGetProperty("rationale", out var rationaleEl) ? rationaleEl.SafeGetString() : null;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("<section class=\"board-report-panel\" style=\"margin:1.5rem 0;padding:1.35rem 1.4rem;border:1px solid #dbe4f0;border-radius:18px;background:linear-gradient(180deg,#f8fbff,#ffffff);box-shadow:0 14px 32px rgba(15,23,42,.07);\">");
|
||||
sb.AppendLine($"<div style=\"font-size:1.05rem;font-weight:800;color:#0f172a;margin-bottom:.8rem\">{Escape(title ?? "Board Report")}</div>");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(decision))
|
||||
sb.AppendLine($"<div style=\"padding:.85rem 1rem;border-radius:14px;background:#eef2ff;color:#312e81;font-weight:700;margin-bottom:.75rem\">Decision Ask: {MarkdownToHtml(decision)}</div>");
|
||||
if (!string.IsNullOrWhiteSpace(recommendation))
|
||||
sb.AppendLine($"<div style=\"padding:.8rem 1rem;border-radius:14px;background:#ecfdf5;color:#166534;font-weight:700;margin-bottom:.85rem\">Recommendation: {MarkdownToHtml(recommendation)}</div>");
|
||||
if (!string.IsNullOrWhiteSpace(rationale))
|
||||
sb.AppendLine($"<div style=\"color:#334155;line-height:1.7;margin-bottom:.95rem\">{MarkdownToHtml(rationale)}</div>");
|
||||
|
||||
if (s.SafeTryGetProperty("metrics", out var metricsEl) && metricsEl.ValueKind == JsonValueKind.Array && metricsEl.GetArrayLength() > 0)
|
||||
{
|
||||
sb.AppendLine("<div style=\"display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:.85rem;margin:1rem 0;\">");
|
||||
foreach (var metric in metricsEl.EnumerateArray())
|
||||
{
|
||||
var label = metric.SafeTryGetProperty("label", out var labelEl) ? labelEl.SafeGetString() ?? string.Empty : string.Empty;
|
||||
var value = metric.SafeTryGetProperty("value", out var valueEl) ? valueEl.SafeGetString() ?? string.Empty : string.Empty;
|
||||
var note = metric.SafeTryGetProperty("note", out var noteEl) ? noteEl.SafeGetString() ?? string.Empty : string.Empty;
|
||||
sb.AppendLine("<div style=\"border:1px solid #e2e8f0;border-radius:14px;padding:.9rem 1rem;background:#fff;\">");
|
||||
sb.AppendLine($"<div style=\"font-size:.78rem;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.05em\">{Escape(label)}</div>");
|
||||
sb.AppendLine($"<div style=\"font-size:1.35rem;font-weight:800;color:#0f172a;margin-top:.2rem\">{Escape(value)}</div>");
|
||||
if (!string.IsNullOrWhiteSpace(note))
|
||||
sb.AppendLine($"<div style=\"font-size:.82rem;color:#475569;margin-top:.3rem\">{MarkdownToHtml(note)}</div>");
|
||||
sb.AppendLine("</div>");
|
||||
}
|
||||
sb.AppendLine("</div>");
|
||||
}
|
||||
|
||||
if (s.SafeTryGetProperty("risks", out var risksEl) && risksEl.ValueKind == JsonValueKind.Array && risksEl.GetArrayLength() > 0)
|
||||
{
|
||||
sb.AppendLine("<div class=\"board-risk-list\" style=\"margin:1rem 0;\">");
|
||||
sb.AppendLine("<div style=\"font-size:.86rem;font-weight:700;color:#7c2d12;margin-bottom:.35rem\">Key Risks</div>");
|
||||
sb.AppendLine("<ul style=\"margin:0;padding-left:1.2rem;color:#7c2d12;line-height:1.7;\">");
|
||||
foreach (var risk in risksEl.EnumerateArray())
|
||||
sb.AppendLine($"<li>{MarkdownToHtml(risk.SafeGetString() ?? string.Empty)}</li>");
|
||||
sb.AppendLine("</ul>");
|
||||
sb.AppendLine("</div>");
|
||||
}
|
||||
|
||||
if (s.SafeTryGetProperty("next_steps", out var stepsEl) && stepsEl.ValueKind == JsonValueKind.Array && stepsEl.GetArrayLength() > 0)
|
||||
{
|
||||
sb.AppendLine("<div style=\"margin-top:1rem;\">");
|
||||
sb.AppendLine("<div style=\"font-size:.86rem;font-weight:700;color:#475569;margin-bottom:.35rem\">Immediate Next Steps</div>");
|
||||
sb.AppendLine("<ul style=\"margin:0;padding-left:1.2rem;color:#334155;line-height:1.7;\">");
|
||||
foreach (var step in stepsEl.EnumerateArray())
|
||||
sb.AppendLine($"<li>{MarkdownToHtml(step.SafeGetString() ?? string.Empty)}</li>");
|
||||
sb.AppendLine("</ul>");
|
||||
sb.AppendLine("</div>");
|
||||
}
|
||||
|
||||
sb.AppendLine("</section>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string RenderStrategyBrief(JsonElement s)
|
||||
{
|
||||
var title = s.SafeTryGetProperty("title", out var titleEl) ? titleEl.SafeGetString() : "Strategy Brief";
|
||||
var strategicQuestion = s.SafeTryGetProperty("strategic_question", out var questionEl) ? questionEl.SafeGetString() : null;
|
||||
var thesis = s.SafeTryGetProperty("thesis", out var thesisEl) ? thesisEl.SafeGetString() : null;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("<section class=\"strategy-brief-panel\" style=\"margin:1.5rem 0;padding:1.3rem 1.4rem;border:1px solid #e2e8f0;border-radius:18px;background:linear-gradient(180deg,#ffffff,#f8fafc);box-shadow:0 12px 28px rgba(15,23,42,.06);\">");
|
||||
sb.AppendLine($"<div style=\"font-size:1.05rem;font-weight:800;color:#0f172a;margin-bottom:.8rem\">{Escape(title ?? "Strategy Brief")}</div>");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(strategicQuestion))
|
||||
sb.AppendLine($"<div style=\"padding:.8rem 1rem;border-radius:14px;background:#fff7ed;color:#9a3412;font-weight:700;margin-bottom:.75rem\">Strategic Question: {MarkdownToHtml(strategicQuestion)}</div>");
|
||||
if (!string.IsNullOrWhiteSpace(thesis))
|
||||
sb.AppendLine($"<div style=\"padding:.85rem 1rem;border-radius:14px;background:#eff6ff;color:#1d4ed8;font-weight:700;margin-bottom:.95rem\">Core Thesis: {MarkdownToHtml(thesis)}</div>");
|
||||
|
||||
if (s.SafeTryGetProperty("implications", out var implicationsEl) && implicationsEl.ValueKind == JsonValueKind.Array && implicationsEl.GetArrayLength() > 0)
|
||||
{
|
||||
sb.AppendLine("<div style=\"display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:.9rem;margin:1rem 0;\">");
|
||||
foreach (var implication in implicationsEl.EnumerateArray())
|
||||
{
|
||||
sb.AppendLine("<div style=\"border:1px solid #dbe4f0;border-radius:14px;padding:.9rem 1rem;background:#fff;\">");
|
||||
sb.AppendLine($"<div style=\"font-size:.85rem;font-weight:700;color:#475569;margin-bottom:.25rem\">Implication</div>");
|
||||
sb.AppendLine($"<div style=\"color:#334155;line-height:1.65\">{MarkdownToHtml(implication.SafeGetString() ?? string.Empty)}</div>");
|
||||
sb.AppendLine("</div>");
|
||||
}
|
||||
sb.AppendLine("</div>");
|
||||
}
|
||||
|
||||
if (s.SafeTryGetProperty("decisions", out var decisionsEl) && decisionsEl.ValueKind == JsonValueKind.Array && decisionsEl.GetArrayLength() > 0)
|
||||
{
|
||||
sb.AppendLine("<div style=\"margin-top:1rem;\">");
|
||||
sb.AppendLine("<div style=\"font-size:.86rem;font-weight:700;color:#475569;margin-bottom:.35rem\">Decisions to Align</div>");
|
||||
sb.AppendLine("<ol style=\"margin:0;padding-left:1.25rem;color:#334155;line-height:1.7;\">");
|
||||
foreach (var item in decisionsEl.EnumerateArray())
|
||||
sb.AppendLine($"<li>{MarkdownToHtml(item.SafeGetString() ?? string.Empty)}</li>");
|
||||
sb.AppendLine("</ol>");
|
||||
sb.AppendLine("</div>");
|
||||
}
|
||||
|
||||
sb.AppendLine("</section>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string RenderComparison(JsonElement s)
|
||||
{
|
||||
if (!s.SafeTryGetProperty("items", out var items) || items.ValueKind != JsonValueKind.Array)
|
||||
|
||||
Reference in New Issue
Block a user