From e1f6caf11a9dd40dc8600219891f6460073007cd Mon Sep 17 00:00:00 2001 From: lacvet Date: Tue, 14 Apr 2026 23:33:23 +0900 Subject: [PATCH] =?UTF-8?q?HTML=20archetype=C2=B7Excel=20dashboard=C2=B7PP?= =?UTF-8?q?T=20=EA=B3=A8=EB=93=A0=20=ED=9A=8C=EA=B7=80=EB=A5=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EA=B3=A0=EB=8F=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- README.md | 9 ++ docs/DEVELOPMENT.md | 9 ++ .../ArtifactQualityReviewServiceTests.cs | 3 + .../ExcelSkillDashboardSummaryTests.cs | 5 + .../HtmlSkillConsultingSectionsTests.cs | 22 ++++ .../Services/PptxSkillGoldenDeckTests.cs | 116 ++++++++++++++++++ .../Agent/ArtifactQualityReviewService.cs | 12 +- src/AxCopilot/Services/Agent/ExcelSkill.cs | 31 +++++ src/AxCopilot/Services/Agent/HtmlSkill.cs | 113 +++++++++++++++++ 9 files changed, 318 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7fc7f44..be0d4c6 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 7093613..16f2029 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -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 diff --git a/src/AxCopilot.Tests/Services/ArtifactQualityReviewServiceTests.cs b/src/AxCopilot.Tests/Services/ArtifactQualityReviewServiceTests.cs index f17a3bf..082d226 100644 --- a/src/AxCopilot.Tests/Services/ArtifactQualityReviewServiceTests.cs +++ b/src/AxCopilot.Tests/Services/ArtifactQualityReviewServiceTests.cs @@ -17,6 +17,8 @@ public class ArtifactQualityReviewServiceTests
MetricValue
NPS61
+
+

Recommendation

Recommendation text.

Action support text.

Appendix

Reference detail.

More detail.

"""; @@ -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); } diff --git a/src/AxCopilot.Tests/Services/ExcelSkillDashboardSummaryTests.cs b/src/AxCopilot.Tests/Services/ExcelSkillDashboardSummaryTests.cs index f170caf..198b076 100644 --- a/src/AxCopilot.Tests/Services/ExcelSkillDashboardSummaryTests.cs +++ b/src/AxCopilot.Tests/Services/ExcelSkillDashboardSummaryTests.cs @@ -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 { diff --git a/src/AxCopilot.Tests/Services/HtmlSkillConsultingSectionsTests.cs b/src/AxCopilot.Tests/Services/HtmlSkillConsultingSectionsTests.cs index 8a664bc..bebe32e 100644 --- a/src/AxCopilot.Tests/Services/HtmlSkillConsultingSectionsTests.cs +++ b/src/AxCopilot.Tests/Services/HtmlSkillConsultingSectionsTests.cs @@ -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 { diff --git a/src/AxCopilot.Tests/Services/PptxSkillGoldenDeckTests.cs b/src/AxCopilot.Tests/Services/PptxSkillGoldenDeckTests.cs index caf86f2..179a63d 100644 --- a/src/AxCopilot.Tests/Services/PptxSkillGoldenDeckTests.cs +++ b/src/AxCopilot.Tests/Services/PptxSkillGoldenDeckTests.cs @@ -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 + { + } + } + } } diff --git a/src/AxCopilot/Services/Agent/ArtifactQualityReviewService.cs b/src/AxCopilot/Services/Agent/ArtifactQualityReviewService.cs index 38a7437..5d088d8 100644 --- a/src/AxCopilot/Services/Agent/ArtifactQualityReviewService.cs +++ b/src/AxCopilot/Services/Agent/ArtifactQualityReviewService.cs @@ -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); } diff --git a/src/AxCopilot/Services/Agent/ExcelSkill.cs b/src/AxCopilot/Services/Agent/ExcelSkill.cs index 2c2fb65..4976a27 100644 --- a/src/AxCopilot/Services/Agent/ExcelSkill.cs +++ b/src/AxCopilot/Services/Agent/ExcelSkill.cs @@ -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) diff --git a/src/AxCopilot/Services/Agent/HtmlSkill.cs b/src/AxCopilot/Services/Agent/HtmlSkill.cs index 65e35a2..76beb9f 100644 --- a/src/AxCopilot/Services/Agent/HtmlSkill.cs +++ b/src/AxCopilot/Services/Agent/HtmlSkill.cs @@ -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("
"); + sb.AppendLine($"
{Escape(title ?? "Board Report")}
"); + + if (!string.IsNullOrWhiteSpace(decision)) + sb.AppendLine($"
Decision Ask: {MarkdownToHtml(decision)}
"); + if (!string.IsNullOrWhiteSpace(recommendation)) + sb.AppendLine($"
Recommendation: {MarkdownToHtml(recommendation)}
"); + if (!string.IsNullOrWhiteSpace(rationale)) + sb.AppendLine($"
{MarkdownToHtml(rationale)}
"); + + if (s.SafeTryGetProperty("metrics", out var metricsEl) && metricsEl.ValueKind == JsonValueKind.Array && metricsEl.GetArrayLength() > 0) + { + sb.AppendLine("
"); + 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("
"); + sb.AppendLine($"
{Escape(label)}
"); + sb.AppendLine($"
{Escape(value)}
"); + if (!string.IsNullOrWhiteSpace(note)) + sb.AppendLine($"
{MarkdownToHtml(note)}
"); + sb.AppendLine("
"); + } + sb.AppendLine("
"); + } + + if (s.SafeTryGetProperty("risks", out var risksEl) && risksEl.ValueKind == JsonValueKind.Array && risksEl.GetArrayLength() > 0) + { + sb.AppendLine("
"); + sb.AppendLine("
Key Risks
"); + sb.AppendLine("
    "); + foreach (var risk in risksEl.EnumerateArray()) + sb.AppendLine($"
  • {MarkdownToHtml(risk.SafeGetString() ?? string.Empty)}
  • "); + sb.AppendLine("
"); + sb.AppendLine("
"); + } + + if (s.SafeTryGetProperty("next_steps", out var stepsEl) && stepsEl.ValueKind == JsonValueKind.Array && stepsEl.GetArrayLength() > 0) + { + sb.AppendLine("
"); + sb.AppendLine("
Immediate Next Steps
"); + sb.AppendLine("
    "); + foreach (var step in stepsEl.EnumerateArray()) + sb.AppendLine($"
  • {MarkdownToHtml(step.SafeGetString() ?? string.Empty)}
  • "); + sb.AppendLine("
"); + sb.AppendLine("
"); + } + + sb.AppendLine("
"); + 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("
"); + sb.AppendLine($"
{Escape(title ?? "Strategy Brief")}
"); + + if (!string.IsNullOrWhiteSpace(strategicQuestion)) + sb.AppendLine($"
Strategic Question: {MarkdownToHtml(strategicQuestion)}
"); + if (!string.IsNullOrWhiteSpace(thesis)) + sb.AppendLine($"
Core Thesis: {MarkdownToHtml(thesis)}
"); + + if (s.SafeTryGetProperty("implications", out var implicationsEl) && implicationsEl.ValueKind == JsonValueKind.Array && implicationsEl.GetArrayLength() > 0) + { + sb.AppendLine("
"); + foreach (var implication in implicationsEl.EnumerateArray()) + { + sb.AppendLine("
"); + sb.AppendLine($"
Implication
"); + sb.AppendLine($"
{MarkdownToHtml(implication.SafeGetString() ?? string.Empty)}
"); + sb.AppendLine("
"); + } + sb.AppendLine("
"); + } + + if (s.SafeTryGetProperty("decisions", out var decisionsEl) && decisionsEl.ValueKind == JsonValueKind.Array && decisionsEl.GetArrayLength() > 0) + { + sb.AppendLine("
"); + sb.AppendLine("
Decisions to Align
"); + sb.AppendLine("
    "); + foreach (var item in decisionsEl.EnumerateArray()) + sb.AppendLine($"
  1. {MarkdownToHtml(item.SafeGetString() ?? string.Empty)}
  2. "); + sb.AppendLine("
"); + sb.AppendLine("
"); + } + + sb.AppendLine("
"); + return sb.ToString(); + } + private static string RenderComparison(JsonElement s) { if (!s.SafeTryGetProperty("items", out var items) || items.ValueKind != JsonValueKind.Array)