using System.Text.RegularExpressions; namespace AxCopilot.Services.Agent; public enum ArtifactReviewSeverity { Info, Warning, Critical, } public sealed record ArtifactReviewIssue(string Message, ArtifactReviewSeverity Severity); public sealed record ArtifactQualityReport( string ArtifactType, int Score, IReadOnlyList Strengths, IReadOnlyList Issues) { public string ToToolSummary() { var strengths = Strengths.Take(3).ToList(); var issues = Issues .OrderByDescending(issue => issue.Severity) .Take(3) .Select(issue => issue.Message) .ToList(); var parts = new List { $"Quality score {Score}/100" }; if (strengths.Count > 0) parts.Add("Strengths: " + string.Join(", ", strengths)); var slideAlertCount = Issues.Count(issue => issue.Message.StartsWith("Slide ", StringComparison.OrdinalIgnoreCase)); if (slideAlertCount > 0) parts.Add($"Slide alerts: {slideAlertCount}"); if (issues.Count > 0) parts.Add("Needs work: " + string.Join(", ", issues)); else parts.Add("Needs work: none"); return string.Join(" | ", parts); } } public sealed record StructuredDocumentReviewInput( string Title, int SectionCount, int BodyCharacterCount, int TableCount, int ListCount, int CalloutCount, int HighlightCount, int IconCount, bool HasCoverPage, bool HasTableOfContents, bool HasTemplate, bool HasHeaderFooter, bool HasExecutiveSummarySection, bool HasRecommendationSection, bool HasAppendixSection); public sealed record WorkbookReviewInput( string Title, int SheetCount, int DetailSheetCount, int DataRowCount, int FormulaCount, int HyperlinkCount, int DataValidationCount, int ConditionalFormattingCount, bool HasSummarySheet, bool HasDashboardSheet, bool HasHighlightSection, bool HasActionSection, bool HasScorecardSection, bool HasDecisionSection, bool HasSheetSummarySection, bool HasTrendSection, bool HasVarianceSection, bool HasDashboardTileSection); public static class ArtifactQualityReviewService { private static readonly string[] ExecutiveKeywords = [ "executive summary", "summary", "\uC694\uC57D", "\uD575\uC2EC \uC694\uC57D", ]; private static readonly string[] RecommendationKeywords = [ "recommendation", "proposal", "next steps", "action plan", "\uAD8C\uACE0", "\uC81C\uC548", "\uC2E4\uD589", ]; private static readonly string[] AppendixKeywords = [ "appendix", "reference", "supplement", "\uBD80\uB85D", "\uCC38\uACE0", ]; public static ArtifactQualityReport ReviewHtml( string title, string html, bool hasCover, bool hasTableOfContents, bool printReady, bool hasPrintFrame = false) { title ??= string.Empty; html ??= string.Empty; var strengths = new List(); var issues = new List(); var sectionCount = Regex.Matches(html, @" 0 ? 1 : 0) + (strategyBriefCount > 0 ? 1 : 0) + (comparisonCount > 0 ? 1 : 0) + (decisionMatrixCount > 0 ? 1 : 0) + (metricStripCount > 0 ? 1 : 0) + (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 + tableCount + calloutCount + comparisonCount + decisionMatrixCount + metricStripCount + roadmapCount + matrixCount + decisionCount + kpiPanelCount + evidenceCardCount + boardPanelCount + strategyBriefCount + (kpiCount > 0 ? 1 : 0); var placeholderCount = CountPlaceholders(html); if (hasCover) strengths.Add("Includes cover page"); if (hasTableOfContents) strengths.Add("Includes table of contents"); 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 || decisionMatrixCount > 0 || metricStripCount > 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 (decisionMatrixCount > 0) strengths.Add("Includes decision matrix"); if (metricStripCount > 0) strengths.Add("Includes metric strip"); if (html.Length < 1800) issues.Add(new("Body content may be too short for an executive-quality document.", ArtifactReviewSeverity.Warning)); if (majorSectionEstimate < 4) 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 + decisionMatrixCount + metricStripCount + 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)); if (!ContainsAny(html, ExecutiveKeywords)) issues.Add(new("Executive summary or summary section is missing.", ArtifactReviewSeverity.Warning)); if (printReady && !hasPrintFrame) issues.Add(new("Print-ready export is missing a print header/footer frame.", ArtifactReviewSeverity.Info)); if (printReady && majorSectionEstimate >= 4 && decisionCount + evidenceCardCount == 0) issues.Add(new("Print-ready business report would benefit from decision summary or evidence cards.", ArtifactReviewSeverity.Warning)); if (printReady && !hasCover && majorSectionEstimate >= 5) 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 (boardPanelCount > 0 && decisionMatrixCount == 0 && comparisonCount == 0) issues.Add(new("Board-ready report would benefit from a comparison or decision matrix block.", 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 (strategyBriefCount > 0 && decisionMatrixCount == 0) issues.Add(new("Strategy brief would be stronger with a decision matrix for option trade-offs.", 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)); if (metricStripCount > 0 && decisionCount + boardPanelCount == 0) issues.Add(new("Metric strip should connect to an explicit decision, recommendation, or board summary block.", ArtifactReviewSeverity.Info)); return BuildReport("html", strengths, issues); } public static ArtifactQualityReport ReviewStructuredDocument(StructuredDocumentReviewInput input) { var strengths = new List(); var issues = new List(); if (input.HasTemplate) strengths.Add("Uses template-based styling"); if (input.HasCoverPage) strengths.Add("Includes cover page"); if (input.HasTableOfContents) strengths.Add("Includes table of contents"); if (input.HasHeaderFooter) strengths.Add("Includes header/footer"); if (input.SectionCount >= 5) strengths.Add($"Contains {input.SectionCount} major sections"); if (input.TableCount > 0) strengths.Add($"Includes {input.TableCount} table(s)"); if (input.CalloutCount + input.HighlightCount > 0) strengths.Add("Uses emphasis blocks"); if (input.ListCount > 0) strengths.Add("Uses structured list sections"); if (input.BodyCharacterCount < 1400) issues.Add(new("Body content may be too short for an executive document.", ArtifactReviewSeverity.Warning)); if (input.SectionCount < 4) issues.Add(new("Major section count is low for a business document.", ArtifactReviewSeverity.Warning)); if (input.SectionCount >= 5 && !input.HasCoverPage) issues.Add(new("Client-facing document could benefit from a cover page.", ArtifactReviewSeverity.Info)); if (input.SectionCount >= 5 && !input.HasTableOfContents) issues.Add(new("Long document could benefit from a table of contents.", ArtifactReviewSeverity.Info)); if (input.SectionCount >= 6 && !input.HasTemplate) issues.Add(new("Template-based styling could improve consistency for a longer document.", ArtifactReviewSeverity.Info)); if (input.SectionCount >= 4 && !input.HasHeaderFooter) issues.Add(new("Header or footer metadata is limited for a business document.", ArtifactReviewSeverity.Info)); if (!input.HasExecutiveSummarySection) issues.Add(new("Executive Summary section is missing.", ArtifactReviewSeverity.Warning)); if (!input.HasRecommendationSection) issues.Add(new("Recommendation or next-step section is missing.", ArtifactReviewSeverity.Warning)); if (input.SectionCount >= 6 && !input.HasAppendixSection) issues.Add(new("Appendix or reference section is limited for a long document.", ArtifactReviewSeverity.Info)); if (input.SectionCount >= 5 && input.TableCount == 0) issues.Add(new("Supporting evidence table is limited for a business document.", ArtifactReviewSeverity.Info)); if (input.SectionCount >= 5 && input.CalloutCount + input.HighlightCount == 0) issues.Add(new("Key messages would benefit from callout or highlight blocks.", ArtifactReviewSeverity.Info)); if (input.TableCount + input.ListCount + input.CalloutCount + input.HighlightCount == 0) issues.Add(new("Document structure is mostly plain paragraphs.", ArtifactReviewSeverity.Warning)); return BuildReport("docx", strengths, issues); } public static ArtifactQualityReport ReviewWorkbook(WorkbookReviewInput input) { var strengths = new List(); var issues = new List(); if (input.HasSummarySheet) strengths.Add("Includes summary sheet"); if (input.HasDashboardSheet) strengths.Add("Includes dashboard sheet"); if (input.DetailSheetCount > 1) strengths.Add($"Contains {input.DetailSheetCount} detail sheets"); if (input.FormulaCount > 0) strengths.Add($"Includes {input.FormulaCount} formula cell(s)"); if (input.HyperlinkCount > 0) strengths.Add("Includes navigation links"); if (input.DataValidationCount > 0) strengths.Add("Includes data validation rules"); if (input.ConditionalFormattingCount > 0) strengths.Add("Includes conditional formatting"); if (input.HasHighlightSection) strengths.Add("Includes highlight section"); if (input.HasActionSection) strengths.Add("Includes action section"); if (input.HasScorecardSection) strengths.Add("Includes KPI or scorecard section"); if (input.HasDecisionSection) strengths.Add("Includes decision summary"); if (input.HasSheetSummarySection) strengths.Add("Includes detail sheet summaries"); if (input.SheetCount > 1 && !input.HasSummarySheet) issues.Add(new("Workbook has multiple sheets but no summary sheet.", ArtifactReviewSeverity.Warning)); if (input.DataRowCount >= 10 && input.FormulaCount == 0) issues.Add(new("Workbook has enough data to benefit from more formulas or rollups.", ArtifactReviewSeverity.Warning)); if (input.HasSummarySheet && input.HyperlinkCount == 0) issues.Add(new("Summary sheet does not link to detail sheets.", ArtifactReviewSeverity.Warning)); if (input.DataValidationCount == 0 && input.DataRowCount >= 5) issues.Add(new("Input controls are limited for a workbook with editable data.", ArtifactReviewSeverity.Info)); if (input.ConditionalFormattingCount == 0 && input.DataRowCount >= 8) issues.Add(new("Conditional formatting is limited for a workbook with enough data to prioritize or flag.", ArtifactReviewSeverity.Info)); if (input.HasSummarySheet && !input.HasScorecardSection && !input.HasDecisionSection && !input.HasHighlightSection) issues.Add(new("Summary sheet could better surface KPIs, decisions, or highlights.", ArtifactReviewSeverity.Info)); if (input.HasSummarySheet && input.DetailSheetCount >= 2 && !input.HasDashboardSheet) issues.Add(new("Workbook could benefit from a dashboard sheet to summarize multi-sheet trends.", ArtifactReviewSeverity.Info)); if (input.HasDashboardSheet && !input.HasScorecardSection && !input.HasDecisionSection) issues.Add(new("Dashboard sheet lacks KPI, trend, or decision content.", ArtifactReviewSeverity.Info)); if (input.HasDashboardSheet && !input.HasHighlightSection && !input.HasActionSection) issues.Add(new("Dashboard sheet could better call out highlights or actions.", ArtifactReviewSeverity.Info)); if (input.HasDashboardSheet && input.DetailSheetCount >= 2 && input.HyperlinkCount == 0) issues.Add(new("Dashboard sheet should link to supporting detail sheets.", ArtifactReviewSeverity.Info)); if (input.HasDashboardSheet && input.DetailSheetCount >= 2 && input.FormulaCount < 3) issues.Add(new("Dashboard sheet would benefit from more calculated trend or variance formulas.", ArtifactReviewSeverity.Info)); if (input.HasDashboardSheet && input.DetailSheetCount >= 2 && !input.HasTrendSection && !input.HasVarianceSection) issues.Add(new("Dashboard sheet lacks trend or variance framing for the supporting sheets.", ArtifactReviewSeverity.Info)); if (input.HasDashboardSheet && input.DetailSheetCount >= 2 && !input.HasSheetSummarySection) issues.Add(new("Dashboard workbook should summarize each supporting detail sheet.", ArtifactReviewSeverity.Info)); if (input.HasDecisionSection && !input.HasActionSection) issues.Add(new("Decision summary is present but follow-up actions are limited.", ArtifactReviewSeverity.Info)); if (input.HasDashboardSheet && !input.HasDashboardTileSection && !input.HasHighlightSection) issues.Add(new("Dashboard sheet would benefit from headline tiles or highlight callouts.", ArtifactReviewSeverity.Info)); return BuildReport("xlsx", strengths, issues); } public static bool ContainsBusinessKeyword(string text, params string[] keywords) { return ContainsAny(text, keywords); } private static ArtifactQualityReport BuildReport( string artifactType, List strengths, List issues) { var score = 72; score += Math.Min(18, strengths.Count * 4); score -= issues.Sum(issue => issue.Severity switch { ArtifactReviewSeverity.Critical => 12, ArtifactReviewSeverity.Warning => 6, _ => 2, }); score = Math.Clamp(score, 35, 98); return new ArtifactQualityReport(artifactType, score, strengths, issues); } private static int CountPlaceholders(string text) { if (string.IsNullOrWhiteSpace(text)) return 0; var patterns = new[] { @"\[(todo|placeholder|fill me)\]", @"lorem ipsum", @"\bTBD\b", }; return patterns.Sum(pattern => Regex.Matches(text, pattern, RegexOptions.IgnoreCase).Count); } private static bool ContainsAny(string text, IEnumerable keywords) { return keywords.Any(keyword => text.Contains(keyword, StringComparison.OrdinalIgnoreCase)); } }