문서 고도화 다음 단계를 반영해 엑셀 summary와 DOCX 조립 품질을 끌어올렸습니다
핵심 수정사항: - ExcelSkill summary_sheet에 decision_summary, scorecards, sheet_summaries를 추가해 dashboard형 summary 시트를 생성하도록 확장했습니다. - ArtifactQualityReviewService의 workbook 리뷰 입력을 확장해 KPI/decision/detail summary 존재 여부를 품질 점수와 보완 포인트에 반영했습니다. - DocumentAssemblerTool에 style_map 파라미터를 추가해 template 기반 DOCX 조립에서 title/heading/body 문단 스타일을 실제 Word 스타일로 매핑하도록 개선했습니다. - DocumentAssemblerStyleMapTests, ExcelSkillDashboardSummaryTests를 추가하고 기존 ArtifactQualityReviewServiceTests를 갱신했습니다. 검증 결과: - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_next2\\ -p:IntermediateOutputPath=obj\\verify_doc_next2\\ : 경고 0 / 오류 0 - dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ArtifactQualityReviewServiceTests|DocumentAssemblerStyleMapTests|DocumentAssemblerDocxFeaturesTests|DocumentAssemblerSemanticTests|ExcelSkillDashboardSummaryTests|ExcelSkillSummarySheetTests|ExcelSkillExecutiveSummaryLinkTests|ExcelSkillDataValidationTests|ExcelSkillConditionalFormattingTests" -p:OutputPath=bin\\verify_doc_next2_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_next2_tests\\ : 통과 11
This commit is contained in:
@@ -1822,3 +1822,11 @@ MIT License
|
||||
- 테스트로 `ExcelSkillDataValidationTests`를 추가했고, `DocumentAssemblerDocxFeaturesTests`, `HtmlSkillConsultingSectionsTests`를 확장해 DOCX 템플릿/페이지 번호와 HTML decision/evidence 블록을 회귀 검증했습니다.
|
||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_phase_next\\ -p:IntermediateOutputPath=obj\\verify_doc_phase_next\\` 경고 0 / 오류 0
|
||||
- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "DocumentAssemblerDocxFeaturesTests|DocumentAssemblerSemanticTests|DocumentPlannerWorkbookScaffoldTests|ExcelSkillExecutiveSummaryLinkTests|ExcelSkillSummarySheetTests|ExcelSkillDataValidationTests|HtmlSkillConsultingSectionsTests|DocxSkillTemplateFeaturesTests|DocumentPlannerBusinessDocumentTests" -p:OutputPath=bin\\verify_doc_phase_next_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_phase_next_tests\\` 통과 9
|
||||
|
||||
업데이트: 2026-04-14 23:05 (KST)
|
||||
- 문서 포맷 고도화 다음 단계를 반영했습니다. [ExcelSkill.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ExcelSkill.cs)는 `summary_sheet`에 `decision_summary`, `scorecards`, `sheet_summaries`를 받을 수 있게 확장되어, 단순 KPI 표를 넘어 의사결정 요약과 상세 시트별 상태를 함께 보여주는 dashboard형 summary 시트를 생성합니다.
|
||||
- 같은 파일의 워크북 품질 리뷰 입력도 확장해 summary sheet가 KPI/scorecard, decision summary, detail sheet summary를 실제로 담고 있는지 점수화하도록 [ArtifactQualityReviewService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ArtifactQualityReviewService.cs)를 보강했습니다.
|
||||
- [DocumentAssemblerTool.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/DocumentAssemblerTool.cs)는 `style_map` 파라미터를 지원합니다. 템플릿 기반 DOCX 조립에서 `title`, `heading1`, `heading2`, `body` 문단 스타일을 실제 Word 문단에 연결해 cover title, 섹션 헤딩, 본문 문단이 사내 템플릿 스타일을 더 잘 따르도록 개선했습니다.
|
||||
- 테스트로 [ExcelSkillDashboardSummaryTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ExcelSkillDashboardSummaryTests.cs), [DocumentAssemblerStyleMapTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/DocumentAssemblerStyleMapTests.cs)를 추가했고, [ArtifactQualityReviewServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ArtifactQualityReviewServiceTests.cs)도 새 workbook review 입력 형식에 맞춰 갱신했습니다.
|
||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_next2\\ -p:IntermediateOutputPath=obj\\verify_doc_next2\\` 경고 0 / 오류 0
|
||||
- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ArtifactQualityReviewServiceTests|DocumentAssemblerStyleMapTests|DocumentAssemblerDocxFeaturesTests|DocumentAssemblerSemanticTests|ExcelSkillDashboardSummaryTests|ExcelSkillSummarySheetTests|ExcelSkillExecutiveSummaryLinkTests|ExcelSkillDataValidationTests|ExcelSkillConditionalFormattingTests" -p:OutputPath=bin\\verify_doc_next2_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_next2_tests\\` 통과 11
|
||||
|
||||
@@ -891,3 +891,11 @@ UI ?붿옄???洹쒕え 由ы뙥?좊쭅 ???꾪뿕 ?묒뾽 ??湲곕줉???덉쟾
|
||||
- 테스트로 [ExcelSkillDataValidationTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ExcelSkillDataValidationTests.cs)를 추가했고, [DocumentAssemblerDocxFeaturesTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/DocumentAssemblerDocxFeaturesTests.cs), [HtmlSkillConsultingSectionsTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/HtmlSkillConsultingSectionsTests.cs)를 확장해 DOCX 템플릿/페이지 번호와 HTML decision/evidence 블록을 회귀 검증했습니다.
|
||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_phase_next\\ -p:IntermediateOutputPath=obj\\verify_doc_phase_next\\` 경고 0 / 오류 0
|
||||
- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "DocumentAssemblerDocxFeaturesTests|DocumentAssemblerSemanticTests|DocumentPlannerWorkbookScaffoldTests|ExcelSkillExecutiveSummaryLinkTests|ExcelSkillSummarySheetTests|ExcelSkillDataValidationTests|HtmlSkillConsultingSectionsTests|DocxSkillTemplateFeaturesTests|DocumentPlannerBusinessDocumentTests" -p:OutputPath=bin\\verify_doc_phase_next_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_phase_next_tests\\` 통과 9
|
||||
|
||||
업데이트: 2026-04-14 23:05 (KST)
|
||||
- 문서 고도화 다음 단계를 반영했습니다. [ExcelSkill.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ExcelSkill.cs)는 `summary_sheet`에 `decision_summary`, `scorecards`, `sheet_summaries`를 추가로 받을 수 있게 확장됐고, executive summary sheet에서 의사결정 요청, 핵심 scorecard, 상세 시트별 상태를 순서대로 렌더링합니다.
|
||||
- 워크북 품질 리뷰 입력도 같은 구조를 인식하도록 [ArtifactQualityReviewService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ArtifactQualityReviewService.cs)의 `WorkbookReviewInput`과 `ReviewWorkbook()`를 확장했습니다. 이제 summary sheet가 KPI/decision/detail summary를 충분히 담고 있는지 강점과 보완 포인트로 함께 표시합니다.
|
||||
- [DocumentAssemblerTool.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/DocumentAssemblerTool.cs)는 `style_map` 파라미터를 받아 template-based DOCX assembly에서 `title`, `heading1`, `heading2`, `body` 문단 스타일을 실제 Word 문단에 매핑합니다. cover title, 섹션 헤딩, 본문 문단이 사내 템플릿 스타일을 더 자연스럽게 따라가도록 정리했습니다.
|
||||
- 새 회귀 테스트 [ExcelSkillDashboardSummaryTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ExcelSkillDashboardSummaryTests.cs), [DocumentAssemblerStyleMapTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/DocumentAssemblerStyleMapTests.cs)를 추가했고, [ArtifactQualityReviewServiceTests.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot.Tests/Services/ArtifactQualityReviewServiceTests.cs)도 새 record 시그니처에 맞춰 갱신했습니다.
|
||||
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_next2\\ -p:IntermediateOutputPath=obj\\verify_doc_next2\\` 경고 0 / 오류 0
|
||||
- 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ArtifactQualityReviewServiceTests|DocumentAssemblerStyleMapTests|DocumentAssemblerDocxFeaturesTests|DocumentAssemblerSemanticTests|ExcelSkillDashboardSummaryTests|ExcelSkillSummarySheetTests|ExcelSkillExecutiveSummaryLinkTests|ExcelSkillDataValidationTests|ExcelSkillConditionalFormattingTests" -p:OutputPath=bin\\verify_doc_next2_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_next2_tests\\` 통과 11
|
||||
|
||||
@@ -68,6 +68,9 @@ public class ArtifactQualityReviewServiceTests
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false));
|
||||
|
||||
review.Issues.Should().Contain(issue => issue.Message.Contains("summary sheet", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using AxCopilot.Services.Agent;
|
||||
using DocumentFormat.OpenXml.Packaging;
|
||||
using DocumentFormat.OpenXml.Wordprocessing;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class DocumentAssemblerStyleMapTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithStyleMap_ShouldApplyTemplateParagraphStyles()
|
||||
{
|
||||
var workDir = Path.Combine(Path.GetTempPath(), "ax-doc-assemble-stylemap-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(workDir);
|
||||
var templatePath = Path.Combine(workDir, "style-template.docx");
|
||||
|
||||
try
|
||||
{
|
||||
using (var template = WordprocessingDocument.Create(templatePath, DocumentFormat.OpenXml.WordprocessingDocumentType.Document))
|
||||
{
|
||||
var mainPart = template.AddMainDocumentPart();
|
||||
mainPart.Document = new Document(new Body(new Paragraph(new Run(new Text("Template Placeholder")))));
|
||||
|
||||
var stylePart = mainPart.AddNewPart<StyleDefinitionsPart>();
|
||||
stylePart.Styles = new Styles(
|
||||
new Style { Type = StyleValues.Paragraph, StyleId = "DeckTitle", CustomStyle = true },
|
||||
new Style { Type = StyleValues.Paragraph, StyleId = "DeckHeading1", CustomStyle = true },
|
||||
new Style { Type = StyleValues.Paragraph, StyleId = "DeckBody", CustomStyle = true });
|
||||
stylePart.Styles.Save();
|
||||
mainPart.Document.Save();
|
||||
}
|
||||
|
||||
var tool = new DocumentAssemblerTool();
|
||||
var context = new AgentContext
|
||||
{
|
||||
WorkFolder = workDir,
|
||||
Permission = "Auto",
|
||||
OperationMode = "external",
|
||||
};
|
||||
|
||||
var args = JsonDocument.Parse(
|
||||
"""
|
||||
{
|
||||
"path": "assembled-stylemap.docx",
|
||||
"title": "Board Update",
|
||||
"format": "docx",
|
||||
"template_path": "style-template.docx",
|
||||
"style_map": {
|
||||
"title": "DeckTitle",
|
||||
"heading1": "DeckHeading1",
|
||||
"body": "DeckBody"
|
||||
},
|
||||
"sections": [
|
||||
{ "heading": "1. Executive Summary", "level": 1, "content": "<p>Margins improved across key accounts.</p>" }
|
||||
]
|
||||
}
|
||||
""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
|
||||
using var doc = WordprocessingDocument.Open(Path.Combine(workDir, "assembled-stylemap.docx"), false);
|
||||
var paragraphs = doc.MainDocumentPart!.Document.Body!.Elements<Paragraph>().ToList();
|
||||
|
||||
paragraphs.First(paragraph => paragraph.InnerText == "Board Update")
|
||||
.ParagraphProperties?.ParagraphStyleId?.Val?.Value
|
||||
.Should().Be("DeckTitle");
|
||||
paragraphs.First(paragraph => paragraph.InnerText == "1. Executive Summary")
|
||||
.ParagraphProperties?.ParagraphStyleId?.Val?.Value
|
||||
.Should().Be("DeckHeading1");
|
||||
paragraphs.First(paragraph => paragraph.InnerText.Contains("Margins improved"))
|
||||
.ParagraphProperties?.ParagraphStyleId?.Val?.Value
|
||||
.Should().Be("DeckBody");
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(workDir))
|
||||
Directory.Delete(workDir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using AxCopilot.Services.Agent;
|
||||
using DocumentFormat.OpenXml.Packaging;
|
||||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class ExcelSkillDashboardSummaryTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithDashboardSummary_ShouldRenderDecisionAndSheetSummaryBlocks()
|
||||
{
|
||||
var workDir = Path.Combine(Path.GetTempPath(), "ax-xlsx-dashboard-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(workDir);
|
||||
|
||||
try
|
||||
{
|
||||
var tool = new ExcelSkill();
|
||||
var context = new AgentContext
|
||||
{
|
||||
WorkFolder = workDir,
|
||||
Permission = "Auto",
|
||||
OperationMode = "external",
|
||||
};
|
||||
|
||||
var args = JsonDocument.Parse(
|
||||
"""
|
||||
{
|
||||
"path": "dashboard-review.xlsx",
|
||||
"summary_sheet": {
|
||||
"name": "Summary",
|
||||
"title": "Operating Review",
|
||||
"decision_summary": [
|
||||
{ "label": "Decision Ask", "value": "Approve regional rollout", "owner": "COO" }
|
||||
],
|
||||
"scorecards": [
|
||||
{ "label": "Run-rate", "value": "96%", "status": "On Track", "note": "Ahead of plan" }
|
||||
],
|
||||
"sheet_summaries": [
|
||||
{ "sheet": "Revenue", "status": "Watch", "summary": "SMB margin pressure", "owner": "Sales Ops" }
|
||||
],
|
||||
"highlights": ["Expansion pipeline improved"],
|
||||
"actions": ["Lock next-quarter target"]
|
||||
},
|
||||
"sheets": [
|
||||
{
|
||||
"name": "Revenue",
|
||||
"headers": ["Metric", "Value"],
|
||||
"rows": [["Revenue", 120], ["Margin", 18]]
|
||||
}
|
||||
]
|
||||
}
|
||||
""").RootElement;
|
||||
|
||||
var result = await tool.ExecuteAsync(args, context, CancellationToken.None);
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
result.Output.Should().Contain("Quality score");
|
||||
|
||||
using var doc = SpreadsheetDocument.Open(Path.Combine(workDir, "dashboard-review.xlsx"), false);
|
||||
var workbookPart = doc.WorkbookPart!;
|
||||
var summarySheet = workbookPart.Workbook.Sheets!.Elements<Sheet>().First(sheet => sheet.Name == "Summary");
|
||||
var summaryPart = (WorksheetPart)workbookPart.GetPartById(summarySheet.Id!);
|
||||
var texts = summaryPart.Worksheet.Descendants<Cell>()
|
||||
.Select(cell => cell.CellValue?.Text)
|
||||
.Where(text => !string.IsNullOrWhiteSpace(text))
|
||||
.ToList();
|
||||
|
||||
texts.Should().Contain("Decision Summary");
|
||||
texts.Should().Contain("Approve regional rollout");
|
||||
texts.Should().Contain("Scorecards");
|
||||
texts.Should().Contain("Run-rate");
|
||||
texts.Should().Contain("Revenue");
|
||||
texts.Should().Contain("SMB margin pressure");
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(workDir))
|
||||
Directory.Delete(workDir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,10 @@ public sealed record WorkbookReviewInput(
|
||||
int ConditionalFormattingCount,
|
||||
bool HasSummarySheet,
|
||||
bool HasHighlightSection,
|
||||
bool HasActionSection);
|
||||
bool HasActionSection,
|
||||
bool HasScorecardSection,
|
||||
bool HasDecisionSection,
|
||||
bool HasSheetSummarySection);
|
||||
|
||||
public static class ArtifactQualityReviewService
|
||||
{
|
||||
@@ -192,6 +195,9 @@ public static class ArtifactQualityReviewService
|
||||
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));
|
||||
@@ -203,6 +209,8 @@ public static class ArtifactQualityReviewService
|
||||
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));
|
||||
|
||||
return BuildReport("xlsx", strengths, issues);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@ namespace AxCopilot.Services.Agent;
|
||||
/// </summary>
|
||||
public class DocumentAssemblerTool : IAgentTool
|
||||
{
|
||||
private sealed record AssemblerDocxStyleMap(
|
||||
string? TitleStyle,
|
||||
string? Heading1Style,
|
||||
string? Heading2Style,
|
||||
string? BodyStyle);
|
||||
|
||||
private static string GetDefaultOutputFormat()
|
||||
{
|
||||
var app = System.Windows.Application.Current as App;
|
||||
@@ -61,6 +67,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\"}." },
|
||||
},
|
||||
Required = ["path", "title", "sections"]
|
||||
};
|
||||
@@ -76,6 +83,7 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
var templatePath = args.SafeTryGetProperty("template_path", out var templateEl) ? templateEl.SafeGetString() : null;
|
||||
var headerText = args.SafeTryGetProperty("header", out var hdr) ? hdr.SafeGetString() : null;
|
||||
var footerText = args.SafeTryGetProperty("footer", out var ftr) ? ftr.SafeGetString() : null;
|
||||
var styleMapArg = args.SafeTryGetProperty("style_map", out var styleMapEl) ? styleMapEl : default;
|
||||
var showPageNumbers = args.SafeTryGetProperty("page_numbers", out var pageNumbersEl)
|
||||
? pageNumbersEl.ValueKind == JsonValueKind.True
|
||||
: !string.IsNullOrWhiteSpace(headerText) || !string.IsNullOrWhiteSpace(footerText);
|
||||
@@ -140,7 +148,7 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
switch (format)
|
||||
{
|
||||
case "docx":
|
||||
resultMsg = AssembleDocx(fullPath, title, sections, useToc, coverSubtitle, templateFullPath, headerText, footerText, showPageNumbers);
|
||||
resultMsg = AssembleDocx(fullPath, title, sections, useToc, coverSubtitle, templateFullPath, headerText, footerText, showPageNumbers, styleMapArg);
|
||||
break;
|
||||
case "markdown":
|
||||
resultMsg = AssembleMarkdown(fullPath, title, sections);
|
||||
@@ -259,7 +267,7 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
}
|
||||
|
||||
private string AssembleDocx(string path, string title, List<(string Heading, string Content, int Level)> sections,
|
||||
bool useToc, string? coverSubtitle, string? templatePath, string? headerText, string? footerText, bool showPageNumbers)
|
||||
bool useToc, string? coverSubtitle, string? templatePath, string? headerText, string? footerText, bool showPageNumbers, JsonElement styleMapArg)
|
||||
{
|
||||
var templateApplied = !string.IsNullOrWhiteSpace(templatePath);
|
||||
if (templateApplied)
|
||||
@@ -272,6 +280,7 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
var mainPart = doc.MainDocumentPart ?? doc.AddMainDocumentPart();
|
||||
|
||||
EnsureAssemblerStyles(mainPart, templateApplied);
|
||||
var styleMap = ResolveAssemblerStyleMap(mainPart, styleMapArg);
|
||||
|
||||
var body = InitializeAssemblerBody(mainPart, templateApplied);
|
||||
|
||||
@@ -308,10 +317,13 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
string fontSize = "22",
|
||||
bool bold = false,
|
||||
string? color = null,
|
||||
string? fill = null)
|
||||
string? fill = null,
|
||||
string? styleId = null)
|
||||
{
|
||||
var para = new DocumentFormat.OpenXml.Wordprocessing.Paragraph();
|
||||
var props = new DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties();
|
||||
if (!string.IsNullOrWhiteSpace(styleId))
|
||||
props.ParagraphStyleId = new DocumentFormat.OpenXml.Wordprocessing.ParagraphStyleId { Val = styleId };
|
||||
props.SpacingBetweenLines = new DocumentFormat.OpenXml.Wordprocessing.SpacingBetweenLines
|
||||
{
|
||||
After = "160"
|
||||
@@ -336,18 +348,26 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
para.AppendChild(props);
|
||||
|
||||
var run = new DocumentFormat.OpenXml.Wordprocessing.Run();
|
||||
var runProps = new DocumentFormat.OpenXml.Wordprocessing.RunProperties
|
||||
var applyInlineFormatting = string.IsNullOrWhiteSpace(styleId)
|
||||
|| !string.IsNullOrWhiteSpace(fill)
|
||||
|| bold
|
||||
|| !string.IsNullOrWhiteSpace(color)
|
||||
|| fontSize != "22";
|
||||
if (applyInlineFormatting)
|
||||
{
|
||||
RunFonts = KoreanFonts(),
|
||||
FontSize = new DocumentFormat.OpenXml.Wordprocessing.FontSize { Val = fontSize }
|
||||
};
|
||||
var runProps = new DocumentFormat.OpenXml.Wordprocessing.RunProperties
|
||||
{
|
||||
RunFonts = KoreanFonts(),
|
||||
FontSize = new DocumentFormat.OpenXml.Wordprocessing.FontSize { Val = fontSize }
|
||||
};
|
||||
|
||||
if (bold)
|
||||
runProps.Bold = new DocumentFormat.OpenXml.Wordprocessing.Bold();
|
||||
if (!string.IsNullOrWhiteSpace(color))
|
||||
runProps.Color = new DocumentFormat.OpenXml.Wordprocessing.Color { Val = color };
|
||||
if (bold)
|
||||
runProps.Bold = new DocumentFormat.OpenXml.Wordprocessing.Bold();
|
||||
if (!string.IsNullOrWhiteSpace(color))
|
||||
runProps.Color = new DocumentFormat.OpenXml.Wordprocessing.Color { Val = color };
|
||||
|
||||
run.AppendChild(runProps);
|
||||
run.AppendChild(runProps);
|
||||
}
|
||||
run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text(text)
|
||||
{
|
||||
Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve
|
||||
@@ -356,6 +376,24 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
return para;
|
||||
}
|
||||
|
||||
DocumentFormat.OpenXml.Wordprocessing.Paragraph CreateTitleParagraph(string text)
|
||||
=> !string.IsNullOrWhiteSpace(styleMap.TitleStyle)
|
||||
? CreateParagraph(text, styleId: styleMap.TitleStyle)
|
||||
: CreateParagraph(text, fontSize: "48", bold: true, color: "1F3A5F");
|
||||
|
||||
DocumentFormat.OpenXml.Wordprocessing.Paragraph CreateSectionHeadingParagraph(string text, int level)
|
||||
{
|
||||
var headingStyleId = level <= 1 ? styleMap.Heading1Style : styleMap.Heading2Style;
|
||||
return !string.IsNullOrWhiteSpace(headingStyleId)
|
||||
? CreateParagraph(text, styleId: headingStyleId)
|
||||
: CreateParagraph(text, fontSize: level <= 1 ? "32" : "28", bold: true, color: "2B579A");
|
||||
}
|
||||
|
||||
DocumentFormat.OpenXml.Wordprocessing.Paragraph CreateBodyParagraph(string text)
|
||||
=> !string.IsNullOrWhiteSpace(styleMap.BodyStyle)
|
||||
? CreateParagraph(text, styleId: styleMap.BodyStyle)
|
||||
: CreateParagraph(text);
|
||||
|
||||
DocumentFormat.OpenXml.Wordprocessing.Table CreateTableFromHtml(string tableHtml)
|
||||
{
|
||||
var table = new DocumentFormat.OpenXml.Wordprocessing.Table();
|
||||
@@ -387,7 +425,7 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.AppendChild(CreateParagraph(cellText));
|
||||
cell.AppendChild(CreateBodyParagraph(cellText));
|
||||
}
|
||||
cell.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
|
||||
new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth
|
||||
@@ -415,23 +453,23 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
|
||||
if (Regex.IsMatch(line, @"^#{2,6}\s+"))
|
||||
{
|
||||
body.AppendChild(CreateParagraph(Regex.Replace(line, @"^#{2,6}\s+", ""), fontSize: "26", bold: true, color: "2B579A"));
|
||||
body.AppendChild(CreateSectionHeadingParagraph(Regex.Replace(line, @"^#{2,6}\s+", ""), 2));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Regex.IsMatch(line, @"^[-*]\s+"))
|
||||
{
|
||||
body.AppendChild(CreateParagraph($"• {Regex.Replace(line, @"^[-*]\s+", "")}"));
|
||||
body.AppendChild(CreateBodyParagraph($"• {Regex.Replace(line, @"^[-*]\s+", "")}"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Regex.IsMatch(line, @"^\d+\.\s+"))
|
||||
{
|
||||
body.AppendChild(CreateParagraph(line));
|
||||
body.AppendChild(CreateBodyParagraph(line));
|
||||
continue;
|
||||
}
|
||||
|
||||
body.AppendChild(CreateParagraph(line));
|
||||
body.AppendChild(CreateBodyParagraph(line));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,16 +481,16 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
{
|
||||
var text = ExtractStructuredText(match.Groups[1].Value);
|
||||
var prefix = ordered ? $"{number}. " : "• ";
|
||||
body.AppendChild(CreateParagraph(prefix + text));
|
||||
body.AppendChild(CreateBodyParagraph(prefix + text));
|
||||
number++;
|
||||
}
|
||||
}
|
||||
|
||||
var includeCover = !string.IsNullOrWhiteSpace(coverSubtitle);
|
||||
if (includeCover)
|
||||
AppendAssemblerCoverPage(body, title, coverSubtitle!);
|
||||
AppendAssemblerCoverPage(body, title, coverSubtitle!, styleMap.TitleStyle);
|
||||
else
|
||||
body.AppendChild(CreateParagraph(title, fontSize: "48", bold: true, color: "1F3A5F"));
|
||||
body.AppendChild(CreateTitleParagraph(title));
|
||||
|
||||
if (!includeCover)
|
||||
body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
|
||||
@@ -468,18 +506,7 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
|
||||
foreach (var (heading, content, level) in sections)
|
||||
{
|
||||
var headPara = new DocumentFormat.OpenXml.Wordprocessing.Paragraph();
|
||||
var headRun = new DocumentFormat.OpenXml.Wordprocessing.Run();
|
||||
headRun.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.RunProperties
|
||||
{
|
||||
RunFonts = KoreanFonts(),
|
||||
Bold = new DocumentFormat.OpenXml.Wordprocessing.Bold(),
|
||||
FontSize = new DocumentFormat.OpenXml.Wordprocessing.FontSize { Val = level <= 1 ? "32" : "28" },
|
||||
Color = new DocumentFormat.OpenXml.Wordprocessing.Color { Val = "2B579A" },
|
||||
});
|
||||
headRun.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text(heading));
|
||||
headPara.AppendChild(headRun);
|
||||
body.AppendChild(headPara);
|
||||
body.AppendChild(CreateSectionHeadingParagraph(heading, level));
|
||||
headings.Add(heading);
|
||||
|
||||
AppendStructuredContent(content);
|
||||
@@ -565,7 +592,11 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
}
|
||||
else if (Regex.IsMatch(block, @"^<h", RegexOptions.IgnoreCase))
|
||||
{
|
||||
body.AppendChild(CreateParagraph(ExtractStructuredText(block), fontSize: "26", bold: true, color: "2B579A"));
|
||||
var headingMatch = Regex.Match(block, @"^<h([2-6])", RegexOptions.IgnoreCase);
|
||||
var headingLevel = headingMatch.Success && int.TryParse(headingMatch.Groups[1].Value, out var parsedLevel)
|
||||
? Math.Max(1, parsedLevel - 1)
|
||||
: 2;
|
||||
body.AppendChild(CreateSectionHeadingParagraph(ExtractStructuredText(block), headingLevel));
|
||||
}
|
||||
else if (Regex.IsMatch(block, @"^<(blockquote|div)", RegexOptions.IgnoreCase))
|
||||
{
|
||||
@@ -669,28 +700,67 @@ public class DocumentAssemblerTool : IAgentTool
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendAssemblerCoverPage(DocumentFormat.OpenXml.Wordprocessing.Body body, string title, string subtitle)
|
||||
private static AssemblerDocxStyleMap ResolveAssemblerStyleMap(
|
||||
DocumentFormat.OpenXml.Packaging.MainDocumentPart mainPart,
|
||||
JsonElement styleMapArg)
|
||||
{
|
||||
body.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
|
||||
new DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties
|
||||
if (styleMapArg.ValueKind != JsonValueKind.Object)
|
||||
return new AssemblerDocxStyleMap(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));
|
||||
}
|
||||
|
||||
private static string? FindAssemblerStyle(
|
||||
DocumentFormat.OpenXml.Packaging.MainDocumentPart mainPart,
|
||||
string? styleId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(styleId))
|
||||
return null;
|
||||
|
||||
var styles = mainPart.StyleDefinitionsPart?.Styles;
|
||||
if (styles == null)
|
||||
return null;
|
||||
|
||||
return styles
|
||||
.Elements<DocumentFormat.OpenXml.Wordprocessing.Style>()
|
||||
.Any(style => string.Equals(style.StyleId?.Value, styleId, StringComparison.OrdinalIgnoreCase))
|
||||
? styleId
|
||||
: null;
|
||||
}
|
||||
|
||||
private static void AppendAssemblerCoverPage(DocumentFormat.OpenXml.Wordprocessing.Body body, string title, string subtitle, string? titleStyleId)
|
||||
{
|
||||
var titleParagraphProperties = 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
|
||||
{
|
||||
Before = "1600",
|
||||
After = "120"
|
||||
}
|
||||
Val = DocumentFormat.OpenXml.Wordprocessing.JustificationValues.Center
|
||||
},
|
||||
new DocumentFormat.OpenXml.Wordprocessing.Run(
|
||||
new DocumentFormat.OpenXml.Wordprocessing.RunProperties(
|
||||
new DocumentFormat.OpenXml.Wordprocessing.Bold(),
|
||||
new DocumentFormat.OpenXml.Wordprocessing.FontSize { Val = "44" },
|
||||
new DocumentFormat.OpenXml.Wordprocessing.Color { Val = "1F3A5F" },
|
||||
new DocumentFormat.OpenXml.Wordprocessing.RunFonts { Ascii = "맑은 고딕", HighAnsi = "맑은 고딕", EastAsia = "맑은 고딕" }),
|
||||
new DocumentFormat.OpenXml.Wordprocessing.Text(title))));
|
||||
SpacingBetweenLines = new DocumentFormat.OpenXml.Wordprocessing.SpacingBetweenLines
|
||||
{
|
||||
Before = "1600",
|
||||
After = "120"
|
||||
}
|
||||
};
|
||||
if (!string.IsNullOrWhiteSpace(titleStyleId))
|
||||
titleParagraphProperties.ParagraphStyleId = new DocumentFormat.OpenXml.Wordprocessing.ParagraphStyleId { Val = titleStyleId };
|
||||
|
||||
var titleRun = new DocumentFormat.OpenXml.Wordprocessing.Run();
|
||||
if (string.IsNullOrWhiteSpace(titleStyleId))
|
||||
{
|
||||
titleRun.Append(new DocumentFormat.OpenXml.Wordprocessing.RunProperties(
|
||||
new DocumentFormat.OpenXml.Wordprocessing.Bold(),
|
||||
new DocumentFormat.OpenXml.Wordprocessing.FontSize { Val = "44" },
|
||||
new DocumentFormat.OpenXml.Wordprocessing.Color { Val = "1F3A5F" },
|
||||
new DocumentFormat.OpenXml.Wordprocessing.RunFonts { Ascii = "맑은 고딕", HighAnsi = "맑은 고딕", EastAsia = "맑은 고딕" }));
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -217,7 +217,10 @@ public class ExcelSkill : IAgentTool
|
||||
conditionalFormattingCount,
|
||||
false,
|
||||
false,
|
||||
summaryArg.ValueKind == JsonValueKind.Object));
|
||||
summaryArg.ValueKind == JsonValueKind.Object,
|
||||
false,
|
||||
false,
|
||||
false));
|
||||
features += $"\n{review.ToToolSummary()}";
|
||||
|
||||
return ToolResult.Ok(
|
||||
@@ -305,7 +308,10 @@ public class ExcelSkill : IAgentTool
|
||||
conditionalFormattingCount,
|
||||
true,
|
||||
HasSummaryItems(summarySheet, "highlights"),
|
||||
HasSummaryItems(summarySheet, "actions")));
|
||||
HasSummaryItems(summarySheet, "actions"),
|
||||
HasSummaryItems(summarySheet, "scorecards") || HasSummaryItems(summarySheet, "cards") || HasSummaryItems(summarySheet, "kpis"),
|
||||
HasStructuredSummaryContent(summarySheet, "decision_summary"),
|
||||
HasSummaryItems(summarySheet, "sheet_summaries")));
|
||||
features += $"\n{review.ToToolSummary()}";
|
||||
|
||||
return ToolResult.Ok(
|
||||
@@ -428,7 +434,10 @@ public class ExcelSkill : IAgentTool
|
||||
totalConditionalFormattingCount,
|
||||
summarySheet.ValueKind == JsonValueKind.Object,
|
||||
HasSummaryItems(summarySheet, "highlights"),
|
||||
HasSummaryItems(summarySheet, "actions")));
|
||||
HasSummaryItems(summarySheet, "actions"),
|
||||
HasSummaryItems(summarySheet, "scorecards") || HasSummaryItems(summarySheet, "cards") || HasSummaryItems(summarySheet, "kpis"),
|
||||
HasStructuredSummaryContent(summarySheet, "decision_summary"),
|
||||
HasSummaryItems(summarySheet, "sheet_summaries")));
|
||||
return ToolResult.Ok(
|
||||
$"Excel 파일 생성 완료: {fullPath}\n시트: {totalSheets}개, 총 데이터 행: {totalRows}\n{review.ToToolSummary()}",
|
||||
fullPath);
|
||||
@@ -471,6 +480,9 @@ public class ExcelSkill : IAgentTool
|
||||
|
||||
rowIndex++;
|
||||
|
||||
AppendDecisionSummarySection(summarySheet, sheetData, merges, ref rowIndex);
|
||||
AppendScorecardSection(summarySheet, sheetData, ref rowIndex);
|
||||
|
||||
if (summarySheet.SafeTryGetProperty("kpis", out var kpis) && kpis.ValueKind == JsonValueKind.Array && kpis.GetArrayLength() > 0)
|
||||
{
|
||||
AppendMergedTextRow(sheetData, merges, rowIndex++, "A", "F", "Key KPIs", 1);
|
||||
@@ -497,6 +509,7 @@ public class ExcelSkill : IAgentTool
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
AppendSheetSummarySection(summarySheet, sheetData, ref rowIndex);
|
||||
AppendSummaryTextSection(summarySheet, "highlights", "Key Highlights", sheetData, merges, ref rowIndex);
|
||||
AppendSummaryTextSection(summarySheet, "actions", "Next Actions", sheetData, merges, ref rowIndex);
|
||||
|
||||
@@ -530,6 +543,115 @@ public class ExcelSkill : IAgentTool
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendDecisionSummarySection(JsonElement summarySheet, SheetData sheetData, MergeCells merges, ref uint rowIndex)
|
||||
{
|
||||
if (!summarySheet.SafeTryGetProperty("decision_summary", out var decisionSummary))
|
||||
return;
|
||||
|
||||
var appended = false;
|
||||
if (decisionSummary.ValueKind == JsonValueKind.Array && decisionSummary.GetArrayLength() > 0)
|
||||
{
|
||||
AppendMergedTextRow(sheetData, merges, rowIndex++, "A", "F", "Decision Summary", 1);
|
||||
foreach (var item in decisionSummary.EnumerateArray())
|
||||
{
|
||||
var label = item.SafeTryGetProperty("label", out var labelEl) ? labelEl.SafeGetString() ?? "" : "";
|
||||
var value = item.SafeTryGetProperty("value", out var valueEl) ? valueEl.SafeGetString() ?? "" : "";
|
||||
var owner = item.SafeTryGetProperty("owner", out var ownerEl) ? ownerEl.SafeGetString() ?? "" : "";
|
||||
|
||||
var row = new Row { RowIndex = rowIndex++ };
|
||||
row.Append(CreateSummaryCell("A", row.RowIndex!.Value, label, 1));
|
||||
row.Append(CreateSummaryCell("B", row.RowIndex!.Value, value, 3));
|
||||
row.Append(CreateSummaryCell("C", row.RowIndex!.Value, owner, 0));
|
||||
sheetData.Append(row);
|
||||
appended = true;
|
||||
}
|
||||
}
|
||||
else if (decisionSummary.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
AppendMergedTextRow(sheetData, merges, rowIndex++, "A", "F", "Decision Summary", 1);
|
||||
foreach (var property in decisionSummary.EnumerateObject())
|
||||
{
|
||||
var row = new Row { RowIndex = rowIndex++ };
|
||||
row.Append(CreateSummaryCell("A", row.RowIndex!.Value, property.Name, 1));
|
||||
row.Append(CreateSummaryCell("B", row.RowIndex!.Value, property.Value.SafeGetString() ?? property.Value.ToString(), 3));
|
||||
sheetData.Append(row);
|
||||
appended = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (appended)
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
private static void AppendScorecardSection(JsonElement summarySheet, SheetData sheetData, ref uint rowIndex)
|
||||
{
|
||||
JsonElement scorecards;
|
||||
var hasScorecards = summarySheet.SafeTryGetProperty("scorecards", out scorecards)
|
||||
&& scorecards.ValueKind == JsonValueKind.Array
|
||||
&& scorecards.GetArrayLength() > 0;
|
||||
if (!hasScorecards)
|
||||
{
|
||||
hasScorecards = summarySheet.SafeTryGetProperty("cards", out scorecards)
|
||||
&& scorecards.ValueKind == JsonValueKind.Array
|
||||
&& scorecards.GetArrayLength() > 0;
|
||||
}
|
||||
|
||||
if (!hasScorecards)
|
||||
return;
|
||||
|
||||
var headerRow = new Row { RowIndex = rowIndex++ };
|
||||
headerRow.Append(CreateSummaryCell("A", headerRow.RowIndex!.Value, "Scorecards", 1));
|
||||
headerRow.Append(CreateSummaryCell("B", headerRow.RowIndex!.Value, "Value", 1));
|
||||
headerRow.Append(CreateSummaryCell("C", headerRow.RowIndex!.Value, "Status", 1));
|
||||
headerRow.Append(CreateSummaryCell("D", headerRow.RowIndex!.Value, "Note", 1));
|
||||
sheetData.Append(headerRow);
|
||||
|
||||
var cardIndex = 0;
|
||||
foreach (var card in scorecards.EnumerateArray())
|
||||
{
|
||||
var row = new Row { RowIndex = rowIndex++ };
|
||||
var stripeStyle = cardIndex % 2 == 0 ? (uint)0 : (uint)2;
|
||||
row.Append(CreateSummaryCell("A", row.RowIndex!.Value, card.SafeTryGetProperty("label", out var labelEl) ? labelEl.SafeGetString() ?? "" : "", 1));
|
||||
row.Append(CreateSummaryCell("B", row.RowIndex!.Value, card.SafeTryGetProperty("value", out var valueEl) ? valueEl.SafeGetString() ?? "" : "", 3));
|
||||
row.Append(CreateSummaryCell("C", row.RowIndex!.Value, card.SafeTryGetProperty("status", out var statusEl) ? statusEl.SafeGetString() ?? "" : "", stripeStyle));
|
||||
row.Append(CreateSummaryCell("D", row.RowIndex!.Value, card.SafeTryGetProperty("note", out var noteEl) ? noteEl.SafeGetString() ?? "" : "", stripeStyle));
|
||||
sheetData.Append(row);
|
||||
cardIndex++;
|
||||
}
|
||||
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
private static void AppendSheetSummarySection(JsonElement summarySheet, SheetData sheetData, ref uint rowIndex)
|
||||
{
|
||||
if (!summarySheet.SafeTryGetProperty("sheet_summaries", out var sheetSummaries)
|
||||
|| sheetSummaries.ValueKind != JsonValueKind.Array
|
||||
|| sheetSummaries.GetArrayLength() == 0)
|
||||
return;
|
||||
|
||||
var headerRow = new Row { RowIndex = rowIndex++ };
|
||||
headerRow.Append(CreateSummaryCell("A", headerRow.RowIndex!.Value, "Sheet", 1));
|
||||
headerRow.Append(CreateSummaryCell("B", headerRow.RowIndex!.Value, "Status", 1));
|
||||
headerRow.Append(CreateSummaryCell("C", headerRow.RowIndex!.Value, "Summary", 1));
|
||||
headerRow.Append(CreateSummaryCell("D", headerRow.RowIndex!.Value, "Owner", 1));
|
||||
sheetData.Append(headerRow);
|
||||
|
||||
var summaryIndex = 0;
|
||||
foreach (var sheetSummary in sheetSummaries.EnumerateArray())
|
||||
{
|
||||
var row = new Row { RowIndex = rowIndex++ };
|
||||
var stripeStyle = summaryIndex % 2 == 0 ? (uint)0 : (uint)2;
|
||||
row.Append(CreateSummaryCell("A", row.RowIndex!.Value, sheetSummary.SafeTryGetProperty("sheet", out var sheetEl) ? sheetEl.SafeGetString() ?? "" : "", 1));
|
||||
row.Append(CreateSummaryCell("B", row.RowIndex!.Value, sheetSummary.SafeTryGetProperty("status", out var statusEl) ? statusEl.SafeGetString() ?? "" : "", 3));
|
||||
row.Append(CreateSummaryCell("C", row.RowIndex!.Value, sheetSummary.SafeTryGetProperty("summary", out var summaryEl) ? summaryEl.SafeGetString() ?? "" : "", stripeStyle));
|
||||
row.Append(CreateSummaryCell("D", row.RowIndex!.Value, sheetSummary.SafeTryGetProperty("owner", out var ownerEl) ? ownerEl.SafeGetString() ?? "" : "", stripeStyle));
|
||||
sheetData.Append(row);
|
||||
summaryIndex++;
|
||||
}
|
||||
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -1235,6 +1357,20 @@ public class ExcelSkill : IAgentTool
|
||||
&& items.GetArrayLength() > 0;
|
||||
}
|
||||
|
||||
private static bool HasStructuredSummaryContent(JsonElement summarySheet, string propertyName)
|
||||
{
|
||||
if (summarySheet.ValueKind != JsonValueKind.Object
|
||||
|| !summarySheet.SafeTryGetProperty(propertyName, out var value))
|
||||
return false;
|
||||
|
||||
return value.ValueKind switch
|
||||
{
|
||||
JsonValueKind.Array => value.GetArrayLength() > 0,
|
||||
JsonValueKind.Object => value.EnumerateObject().Any(),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildFeatureList(bool isStyled, bool freezeHeader,
|
||||
bool hasMerges, bool hasSummary, bool hasNumFmts, bool hasAlignments, string? theme)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user