문서 대시보드·DOCX 스타일맵·PPT 골든 회귀를 고도화하고 검증 추가
- ExcelSkill에 summary_sheet 기반 Dashboard 시트 생성을 추가해 Summary -> Dashboard -> Detail 시트 흐름을 지원 - ArtifactQualityReviewService workbook 리뷰에 dashboard 존재 여부를 반영하고 multi-sheet workbook 보완 포인트를 강화 - DocumentAssemblerTool style_map 범위를 cover_subtitle/callout/table_header까지 확장해 템플릿 기반 DOCX 조립 품질을 개선 - Excel/DOCX/PPT 회귀 테스트를 확장하고 PptxSkillGoldenDeckTests를 추가해 strong deck 품질 기준을 고정 - README.md와 docs/DEVELOPMENT.md에 2026-04-14 23:25 (KST) 기준 작업 이력과 검증 명령을 반영 검증 결과 - 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
This commit is contained in:
@@ -68,6 +68,7 @@ public sealed record WorkbookReviewInput(
|
||||
int DataValidationCount,
|
||||
int ConditionalFormattingCount,
|
||||
bool HasSummarySheet,
|
||||
bool HasDashboardSheet,
|
||||
bool HasHighlightSection,
|
||||
bool HasActionSection,
|
||||
bool HasScorecardSection,
|
||||
@@ -197,6 +198,7 @@ public static class ArtifactQualityReviewService
|
||||
var issues = new List<ArtifactReviewIssue>();
|
||||
|
||||
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");
|
||||
@@ -220,6 +222,8 @@ public static class ArtifactQualityReviewService
|
||||
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));
|
||||
|
||||
return BuildReport("xlsx", strengths, issues);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
string? TitleStyle,
|
||||
string? Heading1Style,
|
||||
string? Heading2Style,
|
||||
string? BodyStyle);
|
||||
string? BodyStyle,
|
||||
string? SubtitleStyle,
|
||||
string? CalloutStyle,
|
||||
string? TableHeaderStyle);
|
||||
|
||||
private static string GetDefaultOutputFormat()
|
||||
{
|
||||
@@ -67,7 +70,7 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
["header"] = new() { Type = "string", Description = "Header text for DOCX output." },
|
||||
["footer"] = new() { Type = "string", Description = "Footer text for DOCX output. Use {page} for page number." },
|
||||
["page_numbers"] = new() { Type = "boolean", Description = "Show page numbers in DOCX footer. Default: true when header or footer is set." },
|
||||
["style_map"] = new() { Type = "object", Description = "Optional paragraph style map for template-based DOCX assembly. Example: {\"title\":\"Title\",\"heading1\":\"Heading1\",\"heading2\":\"Heading2\",\"body\":\"Normal\"}." },
|
||||
["style_map"] = new() { Type = "object", Description = "Optional paragraph style map for template-based DOCX assembly. Example: {\"title\":\"Title\",\"heading1\":\"Heading1\",\"heading2\":\"Heading2\",\"body\":\"Normal\",\"cover_subtitle\":\"Subtitle\",\"callout\":\"Quote\",\"table_header\":\"TableHeader\"}." },
|
||||
},
|
||||
Required = ["path", "title", "sections"]
|
||||
};
|
||||
@@ -421,7 +424,9 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
var cell = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
|
||||
if (isHeader)
|
||||
{
|
||||
cell.AppendChild(CreateParagraph(cellText, fontSize: "21", bold: true, color: "1F3A5F", fill: "E8EEF8"));
|
||||
cell.AppendChild(!string.IsNullOrWhiteSpace(styleMap.TableHeaderStyle)
|
||||
? CreateParagraph(cellText, fontSize: "21", bold: true, color: "1F3A5F", fill: "E8EEF8", styleId: styleMap.TableHeaderStyle)
|
||||
: CreateParagraph(cellText, fontSize: "21", bold: true, color: "1F3A5F", fill: "E8EEF8"));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -488,7 +493,7 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
|
||||
var includeCover = !string.IsNullOrWhiteSpace(coverSubtitle);
|
||||
if (includeCover)
|
||||
AppendAssemblerCoverPage(body, title, coverSubtitle!, styleMap.TitleStyle);
|
||||
AppendAssemblerCoverPage(body, title, coverSubtitle!, styleMap.TitleStyle, styleMap.SubtitleStyle);
|
||||
else
|
||||
body.AppendChild(CreateTitleParagraph(title));
|
||||
|
||||
@@ -601,7 +606,13 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
else if (Regex.IsMatch(block, @"^<(blockquote|div)", RegexOptions.IgnoreCase))
|
||||
{
|
||||
var fill = Regex.IsMatch(block, "highlight-box", RegexOptions.IgnoreCase) ? "FFF7DA" : "EDF4FF";
|
||||
body.AppendChild(CreateParagraph(ExtractStructuredText(block), fontSize: "21", bold: true, color: "1F3A5F", fill: fill));
|
||||
body.AppendChild(CreateParagraph(
|
||||
ExtractStructuredText(block),
|
||||
fontSize: "21",
|
||||
bold: true,
|
||||
color: "1F3A5F",
|
||||
fill: fill,
|
||||
styleId: styleMap.CalloutStyle));
|
||||
if (Regex.IsMatch(block, "highlight-box", RegexOptions.IgnoreCase))
|
||||
highlightCount++;
|
||||
else
|
||||
@@ -705,13 +716,16 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
JsonElement styleMapArg)
|
||||
{
|
||||
if (styleMapArg.ValueKind != JsonValueKind.Object)
|
||||
return new AssemblerDocxStyleMap(null, null, null, null);
|
||||
return new AssemblerDocxStyleMap(null, null, null, null, null, null, null);
|
||||
|
||||
return new AssemblerDocxStyleMap(
|
||||
FindAssemblerStyle(mainPart, styleMapArg.SafeTryGetProperty("title", out var titleEl) ? titleEl.SafeGetString() : null),
|
||||
FindAssemblerStyle(mainPart, styleMapArg.SafeTryGetProperty("heading1", out var heading1El) ? heading1El.SafeGetString() : null),
|
||||
FindAssemblerStyle(mainPart, styleMapArg.SafeTryGetProperty("heading2", out var heading2El) ? heading2El.SafeGetString() : null),
|
||||
FindAssemblerStyle(mainPart, styleMapArg.SafeTryGetProperty("body", out var bodyEl) ? bodyEl.SafeGetString() : null));
|
||||
FindAssemblerStyle(mainPart, styleMapArg.SafeTryGetProperty("body", out var bodyEl) ? bodyEl.SafeGetString() : null),
|
||||
FindAssemblerStyle(mainPart, styleMapArg.SafeTryGetProperty("cover_subtitle", out var subtitleEl) ? subtitleEl.SafeGetString() : null),
|
||||
FindAssemblerStyle(mainPart, styleMapArg.SafeTryGetProperty("callout", out var calloutEl) ? calloutEl.SafeGetString() : null),
|
||||
FindAssemblerStyle(mainPart, styleMapArg.SafeTryGetProperty("table_header", out var tableHeaderEl) ? tableHeaderEl.SafeGetString() : null));
|
||||
}
|
||||
|
||||
private static string? FindAssemblerStyle(
|
||||
@@ -732,7 +746,7 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
: null;
|
||||
}
|
||||
|
||||
private static void AppendAssemblerCoverPage(DocumentFormat.OpenXml.Wordprocessing.Body body, string title, string subtitle, string? titleStyleId)
|
||||
private static void AppendAssemblerCoverPage(DocumentFormat.OpenXml.Wordprocessing.Body body, string title, string subtitle, string? titleStyleId, string? subtitleStyleId)
|
||||
{
|
||||
var titleParagraphProperties = new DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties
|
||||
{
|
||||
@@ -762,24 +776,30 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
titleRun.Append(new DocumentFormat.OpenXml.Wordprocessing.Text(title));
|
||||
body.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(titleParagraphProperties, titleRun));
|
||||
|
||||
body.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
|
||||
new DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties
|
||||
var subtitleParagraphProperties = new DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties
|
||||
{
|
||||
Justification = new DocumentFormat.OpenXml.Wordprocessing.Justification
|
||||
{
|
||||
Justification = new DocumentFormat.OpenXml.Wordprocessing.Justification
|
||||
{
|
||||
Val = DocumentFormat.OpenXml.Wordprocessing.JustificationValues.Center
|
||||
},
|
||||
SpacingBetweenLines = new DocumentFormat.OpenXml.Wordprocessing.SpacingBetweenLines
|
||||
{
|
||||
After = "260"
|
||||
}
|
||||
Val = DocumentFormat.OpenXml.Wordprocessing.JustificationValues.Center
|
||||
},
|
||||
new DocumentFormat.OpenXml.Wordprocessing.Run(
|
||||
new DocumentFormat.OpenXml.Wordprocessing.RunProperties(
|
||||
new DocumentFormat.OpenXml.Wordprocessing.FontSize { Val = "26" },
|
||||
new DocumentFormat.OpenXml.Wordprocessing.Color { Val = "5B6472" },
|
||||
new DocumentFormat.OpenXml.Wordprocessing.RunFonts { Ascii = "맑은 고딕", HighAnsi = "맑은 고딕", EastAsia = "맑은 고딕" }),
|
||||
new DocumentFormat.OpenXml.Wordprocessing.Text(subtitle))));
|
||||
SpacingBetweenLines = new DocumentFormat.OpenXml.Wordprocessing.SpacingBetweenLines
|
||||
{
|
||||
After = "260"
|
||||
}
|
||||
};
|
||||
if (!string.IsNullOrWhiteSpace(subtitleStyleId))
|
||||
subtitleParagraphProperties.ParagraphStyleId = new DocumentFormat.OpenXml.Wordprocessing.ParagraphStyleId { Val = subtitleStyleId };
|
||||
|
||||
var subtitleRun = new DocumentFormat.OpenXml.Wordprocessing.Run();
|
||||
if (string.IsNullOrWhiteSpace(subtitleStyleId))
|
||||
{
|
||||
subtitleRun.Append(new DocumentFormat.OpenXml.Wordprocessing.RunProperties(
|
||||
new DocumentFormat.OpenXml.Wordprocessing.FontSize { Val = "26" },
|
||||
new DocumentFormat.OpenXml.Wordprocessing.Color { Val = "5B6472" },
|
||||
new DocumentFormat.OpenXml.Wordprocessing.RunFonts { Ascii = "맑은 고딕", HighAnsi = "맑은 고딕", EastAsia = "맑은 고딕" }));
|
||||
}
|
||||
subtitleRun.Append(new DocumentFormat.OpenXml.Wordprocessing.Text(subtitle));
|
||||
body.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(subtitleParagraphProperties, subtitleRun));
|
||||
|
||||
body.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
|
||||
new DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties
|
||||
|
||||
@@ -217,7 +217,8 @@ public class ExcelSkill : IAgentTool
|
||||
conditionalFormattingCount,
|
||||
false,
|
||||
false,
|
||||
summaryArg.ValueKind == JsonValueKind.Object,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false));
|
||||
@@ -259,12 +260,17 @@ public class ExcelSkill : IAgentTool
|
||||
stylesPart.Stylesheet.Save();
|
||||
|
||||
var wbSheets = workbookPart.Workbook.AppendChild(new Sheets());
|
||||
var dashboardSheetName = GetDashboardSheetName(summarySheet);
|
||||
var hasDashboardSheet = HasDashboardSheet(summarySheet);
|
||||
var summaryLinkedSheets = hasDashboardSheet
|
||||
? new List<string> { dashboardSheetName, sheetName }
|
||||
: [sheetName];
|
||||
|
||||
var summaryName = summarySheet.SafeTryGetProperty("name", out var summaryNameEl)
|
||||
? summaryNameEl.SafeGetString() ?? "Summary"
|
||||
: "Summary";
|
||||
var summaryPart = workbookPart.AddNewPart<WorksheetPart>();
|
||||
WriteExecutiveSummarySheet(summaryPart, summarySheet, [sheetName]);
|
||||
WriteExecutiveSummarySheet(summaryPart, summarySheet, summaryLinkedSheets);
|
||||
wbSheets.Append(new Sheet
|
||||
{
|
||||
Id = workbookPart.GetIdOfPart(summaryPart),
|
||||
@@ -272,6 +278,19 @@ public class ExcelSkill : IAgentTool
|
||||
Name = summaryName,
|
||||
});
|
||||
|
||||
uint nextSheetId = 2;
|
||||
if (hasDashboardSheet)
|
||||
{
|
||||
var dashboardPart = workbookPart.AddNewPart<WorksheetPart>();
|
||||
WriteDashboardSheet(dashboardPart, summarySheet, [sheetName]);
|
||||
wbSheets.Append(new Sheet
|
||||
{
|
||||
Id = workbookPart.GetIdOfPart(dashboardPart),
|
||||
SheetId = nextSheetId++,
|
||||
Name = dashboardSheetName,
|
||||
});
|
||||
}
|
||||
|
||||
var worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
|
||||
worksheetPart.Worksheet = new Worksheet();
|
||||
|
||||
@@ -287,7 +306,7 @@ public class ExcelSkill : IAgentTool
|
||||
wbSheets.Append(new Sheet
|
||||
{
|
||||
Id = workbookPart.GetIdOfPart(worksheetPart),
|
||||
SheetId = 2,
|
||||
SheetId = nextSheetId,
|
||||
Name = sheetName,
|
||||
});
|
||||
|
||||
@@ -299,14 +318,15 @@ public class ExcelSkill : IAgentTool
|
||||
numFmts.Count > 0, alignments.Count > 0, themeName);
|
||||
var review = ArtifactQualityReviewService.ReviewWorkbook(new WorkbookReviewInput(
|
||||
summaryName,
|
||||
2,
|
||||
hasDashboardSheet ? 3 : 2,
|
||||
1,
|
||||
rowCount,
|
||||
CountFormulaCells(rows),
|
||||
1,
|
||||
summaryLinkedSheets.Count,
|
||||
validationCount,
|
||||
conditionalFormattingCount,
|
||||
true,
|
||||
hasDashboardSheet,
|
||||
HasSummaryItems(summarySheet, "highlights"),
|
||||
HasSummaryItems(summarySheet, "actions"),
|
||||
HasSummaryItems(summarySheet, "scorecards") || HasSummaryItems(summarySheet, "cards") || HasSummaryItems(summarySheet, "kpis") || HasSummaryItems(summarySheet, "trend_series"),
|
||||
@@ -356,6 +376,8 @@ public class ExcelSkill : IAgentTool
|
||||
var totalValidationCount = 0;
|
||||
var totalConditionalFormattingCount = 0;
|
||||
var detailSheetNames = new List<string>();
|
||||
var dashboardSheetName = GetDashboardSheetName(summarySheet);
|
||||
var hasDashboardSheet = HasDashboardSheet(summarySheet);
|
||||
|
||||
foreach (var sheetDef in sheetsArr.EnumerateArray())
|
||||
{
|
||||
@@ -371,13 +393,28 @@ public class ExcelSkill : IAgentTool
|
||||
? summaryNameEl.SafeGetString() ?? "Summary"
|
||||
: "Summary";
|
||||
var summaryPart = workbookPart.AddNewPart<WorksheetPart>();
|
||||
WriteExecutiveSummarySheet(summaryPart, summarySheet, detailSheetNames);
|
||||
var summaryLinkedSheets = hasDashboardSheet
|
||||
? new[] { dashboardSheetName }.Concat(detailSheetNames).ToList()
|
||||
: detailSheetNames;
|
||||
WriteExecutiveSummarySheet(summaryPart, summarySheet, summaryLinkedSheets);
|
||||
wbSheets.Append(new Sheet
|
||||
{
|
||||
Id = workbookPart.GetIdOfPart(summaryPart),
|
||||
SheetId = sheetId++,
|
||||
Name = summaryName,
|
||||
});
|
||||
|
||||
if (hasDashboardSheet)
|
||||
{
|
||||
var dashboardPart = workbookPart.AddNewPart<WorksheetPart>();
|
||||
WriteDashboardSheet(dashboardPart, summarySheet, detailSheetNames);
|
||||
wbSheets.Append(new Sheet
|
||||
{
|
||||
Id = workbookPart.GetIdOfPart(dashboardPart),
|
||||
SheetId = sheetId++,
|
||||
Name = dashboardSheetName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var sheetDef in sheetsArr.EnumerateArray())
|
||||
@@ -425,14 +462,15 @@ public class ExcelSkill : IAgentTool
|
||||
workbookPart.Workbook.Save();
|
||||
var review = ArtifactQualityReviewService.ReviewWorkbook(new WorkbookReviewInput(
|
||||
fullPath,
|
||||
totalSheets + (summarySheet.ValueKind == JsonValueKind.Object ? 1 : 0),
|
||||
totalSheets + (summarySheet.ValueKind == JsonValueKind.Object ? 1 : 0) + (hasDashboardSheet ? 1 : 0),
|
||||
totalSheets,
|
||||
totalRows,
|
||||
totalFormulaCount,
|
||||
summarySheet.ValueKind == JsonValueKind.Object ? detailSheetNames.Count : 0,
|
||||
summarySheet.ValueKind == JsonValueKind.Object ? detailSheetNames.Count + (hasDashboardSheet ? 1 : 0) : 0,
|
||||
totalValidationCount,
|
||||
totalConditionalFormattingCount,
|
||||
summarySheet.ValueKind == JsonValueKind.Object,
|
||||
hasDashboardSheet,
|
||||
HasSummaryItems(summarySheet, "highlights"),
|
||||
HasSummaryItems(summarySheet, "actions"),
|
||||
HasSummaryItems(summarySheet, "scorecards") || HasSummaryItems(summarySheet, "cards") || HasSummaryItems(summarySheet, "kpis") || HasSummaryItems(summarySheet, "trend_series"),
|
||||
@@ -544,6 +582,72 @@ public class ExcelSkill : IAgentTool
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteDashboardSheet(WorksheetPart worksheetPart, JsonElement summarySheet, IReadOnlyList<string> detailSheetNames)
|
||||
{
|
||||
worksheetPart.Worksheet = new Worksheet();
|
||||
|
||||
var columns = new Columns(
|
||||
new Column { Min = 1, Max = 1, Width = 22, CustomWidth = true },
|
||||
new Column { Min = 2, Max = 2, Width = 18, CustomWidth = true },
|
||||
new Column { Min = 3, Max = 3, Width = 18, CustomWidth = true },
|
||||
new Column { Min = 4, Max = 4, Width = 22, CustomWidth = true },
|
||||
new Column { Min = 5, Max = 5, Width = 18, CustomWidth = true },
|
||||
new Column { Min = 6, Max = 6, Width = 30, CustomWidth = true });
|
||||
worksheetPart.Worksheet.Append(columns);
|
||||
|
||||
var sheetData = new SheetData();
|
||||
worksheetPart.Worksheet.Append(sheetData);
|
||||
|
||||
var merges = new MergeCells();
|
||||
var hyperlinks = new Hyperlinks();
|
||||
uint rowIndex = 1;
|
||||
|
||||
var baseTitle = summarySheet.SafeTryGetProperty("title", out var titleEl)
|
||||
? titleEl.SafeGetString() ?? "Executive Summary"
|
||||
: "Executive Summary";
|
||||
var subtitle = summarySheet.SafeTryGetProperty("subtitle", out var subtitleEl)
|
||||
? subtitleEl.SafeGetString() ?? string.Empty
|
||||
: string.Empty;
|
||||
|
||||
AppendMergedTextRow(sheetData, merges, rowIndex++, "A", "F", $"{baseTitle} Dashboard", 1);
|
||||
if (!string.IsNullOrWhiteSpace(subtitle))
|
||||
AppendMergedTextRow(sheetData, merges, rowIndex++, "A", "F", subtitle, 3);
|
||||
|
||||
rowIndex++;
|
||||
|
||||
AppendDecisionSummarySection(summarySheet, sheetData, merges, ref rowIndex);
|
||||
AppendScorecardSection(summarySheet, sheetData, ref rowIndex);
|
||||
AppendTrendSection(summarySheet, sheetData, ref rowIndex);
|
||||
AppendSheetSummarySection(summarySheet, sheetData, ref rowIndex);
|
||||
AppendSummaryTextSection(summarySheet, "actions", "Priority Actions", sheetData, merges, ref rowIndex);
|
||||
|
||||
if (detailSheetNames.Count > 0)
|
||||
{
|
||||
AppendMergedTextRow(sheetData, merges, rowIndex++, "A", "F", "Linked Detail Sheets", 1);
|
||||
var startRow = rowIndex;
|
||||
foreach (var detailSheetName in detailSheetNames)
|
||||
AppendMergedTextRow(sheetData, merges, rowIndex++, "A", "F", $"Open: {detailSheetName}", 0);
|
||||
|
||||
for (var i = 0; i < detailSheetNames.Count; i++)
|
||||
{
|
||||
hyperlinks.Append(new Hyperlink
|
||||
{
|
||||
Reference = $"A{startRow + (uint)i}",
|
||||
Location = $"'{detailSheetNames[i]}'!A1",
|
||||
Display = detailSheetNames[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (merges.HasChildren)
|
||||
worksheetPart.Worksheet.InsertAfter(merges, sheetData);
|
||||
if (hyperlinks.HasChildren)
|
||||
{
|
||||
var anchor = merges.HasChildren ? (OpenXmlElement)merges : sheetData;
|
||||
worksheetPart.Worksheet.InsertAfter(hyperlinks, anchor);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendDecisionSummarySection(JsonElement summarySheet, SheetData sheetData, MergeCells merges, ref uint rowIndex)
|
||||
{
|
||||
if (!summarySheet.SafeTryGetProperty("decision_summary", out var decisionSummary))
|
||||
@@ -685,6 +789,31 @@ public class ExcelSkill : IAgentTool
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
private static bool HasDashboardSheet(JsonElement summarySheet)
|
||||
{
|
||||
if (summarySheet.ValueKind != JsonValueKind.Object)
|
||||
return false;
|
||||
|
||||
if (summarySheet.SafeTryGetProperty("dashboard_sheet_name", out var dashboardNameEl)
|
||||
&& dashboardNameEl.ValueKind == JsonValueKind.String
|
||||
&& !string.IsNullOrWhiteSpace(dashboardNameEl.SafeGetString()))
|
||||
return true;
|
||||
|
||||
return summarySheet.SafeTryGetProperty("trend_series", out var trendSeries)
|
||||
&& trendSeries.ValueKind == JsonValueKind.Array
|
||||
&& trendSeries.GetArrayLength() > 0;
|
||||
}
|
||||
|
||||
private static string GetDashboardSheetName(JsonElement summarySheet)
|
||||
{
|
||||
if (summarySheet.SafeTryGetProperty("dashboard_sheet_name", out var dashboardNameEl)
|
||||
&& dashboardNameEl.ValueKind == JsonValueKind.String
|
||||
&& !string.IsNullOrWhiteSpace(dashboardNameEl.SafeGetString()))
|
||||
return dashboardNameEl.SafeGetString()!;
|
||||
|
||||
return "Dashboard";
|
||||
}
|
||||
|
||||
private static void AppendSummaryTextSection(JsonElement summarySheet, string key, string heading, SheetData sheetData, MergeCells merges, ref uint rowIndex)
|
||||
{
|
||||
if (!summarySheet.SafeTryGetProperty(key, out var items) || items.ValueKind != JsonValueKind.Array || items.GetArrayLength() == 0)
|
||||
|
||||
Reference in New Issue
Block a user