?? ??? 3?? DOCX ????XLSX ?? ???HTML ???? ?? ??

- DocumentAssemblerTool? template_path/page_numbers? ??? DOCX ??? ??, ??????????? ?? ??? ??
- ExcelSkill? data_validations? ???? ??/??/?? ?? ??? ?? ??? ?? ?? ?? ??
- HtmlSkill? ArtifactQualityReviewService? decision_summary/evidence_cards ??? ?? ?? ?? ??? ??
- DocumentAssemblerDocxFeaturesTests, HtmlSkillConsultingSectionsTests, ExcelSkillDataValidationTests? ?? ??? ??
- README.md? docs/DEVELOPMENT.md? 2026-04-14 22:28 (KST) ???? ??

??:
- 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)
This commit is contained in:
2026-04-14 22:31:15 +09:00
parent 1ad5eea32e
commit 5607f6391e
9 changed files with 380 additions and 39 deletions

View File

@@ -42,8 +42,9 @@ public class ExcelSkill : IAgentTool
"Example: {\"name\":\"Summary\",\"title\":\"2026 운영 리뷰\",\"subtitle\":\"핵심 KPI와 후속 과제\",\"kpis\":[{\"label\":\"Revenue\",\"value\":\"12%\",\"trend\":\"YoY\",\"note\":\"Strong\"}],\"highlights\":[\"핵심 인사이트 1\",\"핵심 인사이트 2\"],\"actions\":[\"즉시 과제 1\",\"즉시 과제 2\"]}"
},
["number_formats"] = new() { Type = "array", Description = "Number format per column index. Supported: 'currency' (#,##0\"원\"), 'percent' (0.00%), 'decimal' (#,##0.00), 'integer' (#,##0), 'date' (yyyy-mm-dd), or any custom Excel format string. e.g. [\"text\",\"integer\",\"currency\",\"percent\"]", Items = new() { Type = "string" } },
["data_validations"] = new() { Type = "array", Description = "Validation rules such as [{\"range\":\"E2:E100\",\"type\":\"list\",\"formula1\":\"\\\"Open,In Progress,Done\\\"\",\"allow_blank\":true,\"prompt\":\"Select status\"}].", Items = new() { Type = "object" } },
["col_alignments"] = new() { Type = "array", Description = "Horizontal alignment per column: 'left', 'center', 'right'. Headers always center-aligned.", Items = new() { Type = "string" } },
["sheets"] = new() { Type = "array", Description = "Multi-sheet mode: array of sheet objects [{name, headers, rows, style?, theme?, col_widths?, freeze_header?, number_formats?, col_alignments?, merges?, summary_row?}]. When present, overrides top-level headers/rows/sheet_name.", Items = new() { Type = "object" } },
["sheets"] = new() { Type = "array", Description = "Multi-sheet mode: array of sheet objects [{name, headers, rows, style?, theme?, col_widths?, freeze_header?, number_formats?, col_alignments?, merges?, summary_row?, data_validations?}]. When present, overrides top-level headers/rows/sheet_name.", Items = new() { Type = "object" } },
},
Required = []
};
@@ -183,10 +184,11 @@ public class ExcelSkill : IAgentTool
var summaryArg = args.SafeTryGetProperty("summary_row", out var sumEl) ? sumEl : default;
var mergesArg = args.SafeTryGetProperty("merges", out var mergeEl) ? mergeEl : default;
var validationsArg = args.SafeTryGetProperty("data_validations", out var validationsEl) ? validationsEl : default;
var rowCount = WriteSheetContent(worksheetPart, args, sheetName, headers, rows,
var (rowCount, validationCount) = WriteSheetContent(worksheetPart, args, sheetName, headers, rows,
isStyled, freezeHeader, theme, numFmts, alignments, customFmts,
summaryArg, mergesArg, colCount);
summaryArg, mergesArg, validationsArg, colCount);
var wbSheets = workbookPart.Workbook.AppendChild(new Sheets());
wbSheets.Append(new Sheet
@@ -209,7 +211,7 @@ public class ExcelSkill : IAgentTool
rowCount,
CountFormulaCells(rows),
0,
0,
validationCount,
false,
false,
summaryArg.ValueKind == JsonValueKind.Object));
@@ -270,9 +272,10 @@ public class ExcelSkill : IAgentTool
var colCount = headers.GetArrayLength();
var summaryArg = args.SafeTryGetProperty("summary_row", out var sumEl) ? sumEl : default;
var mergesArg = args.SafeTryGetProperty("merges", out var mergeEl) ? mergeEl : default;
var rowCount = WriteSheetContent(worksheetPart, args, sheetName, headers, rows,
var validationsArg = args.SafeTryGetProperty("data_validations", out var validationsEl) ? validationsEl : default;
var (rowCount, validationCount) = WriteSheetContent(worksheetPart, args, sheetName, headers, rows,
isStyled, freezeHeader, theme, numFmts, alignments, customFmts,
summaryArg, mergesArg, colCount);
summaryArg, mergesArg, validationsArg, colCount);
wbSheets.Append(new Sheet
{
@@ -294,7 +297,7 @@ public class ExcelSkill : IAgentTool
rowCount,
CountFormulaCells(rows),
1,
0,
validationCount,
true,
HasSummaryItems(summarySheet, "highlights"),
HasSummaryItems(summarySheet, "actions")));
@@ -339,6 +342,7 @@ public class ExcelSkill : IAgentTool
var totalSheets = 0;
var totalRows = 0;
var totalFormulaCount = 0;
var totalValidationCount = 0;
var detailSheetNames = new List<string>();
foreach (var sheetDef in sheetsArr.EnumerateArray())
@@ -381,13 +385,14 @@ public class ExcelSkill : IAgentTool
var colCount = headers.GetArrayLength();
var summaryArg = sheetDef.SafeTryGetProperty("summary_row", out var sumEl) ? sumEl : default;
var mergesArg = sheetDef.SafeTryGetProperty("merges", out var mergeEl) ? mergeEl : default;
var validationsArg = sheetDef.SafeTryGetProperty("data_validations", out var validationsEl) ? validationsEl : default;
var worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new Worksheet();
var rowCount = WriteSheetContent(worksheetPart, sheetDef, sheetName, headers, rows,
var (rowCount, validationCount) = WriteSheetContent(worksheetPart, sheetDef, sheetName, headers, rows,
isStyled, freezeHeader, theme, numFmts, alignments, combinedCustomFmts,
summaryArg, mergesArg, colCount);
summaryArg, mergesArg, validationsArg, colCount);
wbSheets.Append(new Sheet
{
@@ -400,11 +405,23 @@ public class ExcelSkill : IAgentTool
totalSheets++;
totalRows += rowCount;
totalFormulaCount += CountFormulaCells(rows);
totalValidationCount += validationCount;
}
workbookPart.Workbook.Save();
var review = ArtifactQualityReviewService.ReviewWorkbook(new WorkbookReviewInput(
fullPath,
totalSheets + (summarySheet.ValueKind == JsonValueKind.Object ? 1 : 0),
totalSheets,
totalRows,
totalFormulaCount,
summarySheet.ValueKind == JsonValueKind.Object ? detailSheetNames.Count : 0,
totalValidationCount,
summarySheet.ValueKind == JsonValueKind.Object,
HasSummaryItems(summarySheet, "highlights"),
HasSummaryItems(summarySheet, "actions")));
return ToolResult.Ok(
$"Excel 파일 생성 완료: {fullPath}\n시트: {totalSheets}개, 총 데이터 행: {totalRows}",
$"Excel 파일 생성 완료: {fullPath}\n시트: {totalSheets}개, 총 데이터 행: {totalRows}\n{review.ToToolSummary()}",
fullPath);
}
@@ -534,7 +551,7 @@ public class ExcelSkill : IAgentTool
};
}
private static int WriteSheetContent(
private static (int RowCount, int ValidationCount) WriteSheetContent(
WorksheetPart worksheetPart,
JsonElement args,
string sheetName,
@@ -548,6 +565,7 @@ public class ExcelSkill : IAgentTool
List<(string code, uint id)> customFmtRegistry,
JsonElement summaryArg,
JsonElement mergesArg,
JsonElement validationsArg,
int colCount)
{
// Column widths
@@ -626,19 +644,22 @@ public class ExcelSkill : IAgentTool
AddSummaryRow(sheetData, summaryArg, rowNum, colCount, rowCount, isStyled);
// Cell merges
MergeCells? mergeCellsSection = null;
if (mergesArg.ValueKind == JsonValueKind.Array)
{
var mergeCells = new MergeCells();
mergeCellsSection = new MergeCells();
foreach (var merge in mergesArg.EnumerateArray())
{
var range = merge.SafeGetString();
if (!string.IsNullOrEmpty(range))
mergeCells.Append(new MergeCell { Reference = range });
mergeCellsSection.Append(new MergeCell { Reference = range });
}
if (mergeCells.HasChildren)
worksheetPart.Worksheet.InsertAfter(mergeCells, sheetData);
if (mergeCellsSection.HasChildren)
worksheetPart.Worksheet.InsertAfter(mergeCellsSection, sheetData);
}
var validationCount = AddDataValidations(worksheetPart, sheetData, mergeCellsSection, validationsArg);
// Freeze header
if (freezeHeader)
{
@@ -663,7 +684,64 @@ public class ExcelSkill : IAgentTool
worksheetPart.Worksheet.InsertBefore(sheetViews, insertBefore);
}
return rowCount;
return (rowCount, validationCount);
}
private static int AddDataValidations(WorksheetPart worksheetPart, SheetData sheetData, MergeCells? mergeCells, JsonElement validationsArg)
{
if (validationsArg.ValueKind != JsonValueKind.Array || validationsArg.GetArrayLength() == 0)
return 0;
var dataValidations = new DataValidations();
var count = 0;
foreach (var rule in validationsArg.EnumerateArray())
{
var range = rule.SafeTryGetProperty("range", out var rangeEl) ? rangeEl.SafeGetString() : null;
var typeText = rule.SafeTryGetProperty("type", out var typeEl) ? typeEl.SafeGetString() : null;
var formula1 = rule.SafeTryGetProperty("formula1", out var formulaEl) ? formulaEl.SafeGetString() : null;
if (string.IsNullOrWhiteSpace(range) || string.IsNullOrWhiteSpace(typeText) || string.IsNullOrWhiteSpace(formula1))
continue;
var validation = new DataValidation
{
Type = ResolveValidationType(typeText),
AllowBlank = !(rule.SafeTryGetProperty("allow_blank", out var allowBlankEl) && allowBlankEl.ValueKind == JsonValueKind.False),
ShowInputMessage = rule.SafeTryGetProperty("prompt", out _),
SequenceOfReferences = new ListValue<StringValue> { InnerText = range }
};
if (rule.SafeTryGetProperty("prompt", out var promptEl) && !string.IsNullOrWhiteSpace(promptEl.SafeGetString()))
{
validation.PromptTitle = "Input";
validation.Prompt = promptEl.SafeGetString();
}
validation.Append(new Formula1(formula1));
dataValidations.Append(validation);
count++;
}
if (count == 0)
return 0;
dataValidations.Count = (uint)count;
var anchor = (OpenXmlElement?)mergeCells ?? sheetData;
worksheetPart.Worksheet.InsertAfter(dataValidations, anchor);
return count;
}
private static DataValidationValues ResolveValidationType(string? type)
{
return (type ?? "list").Trim().ToLowerInvariant() switch
{
"whole" or "whole_number" => DataValidationValues.Whole,
"decimal" => DataValidationValues.Decimal,
"date" => DataValidationValues.Date,
"text_length" => DataValidationValues.TextLength,
"custom" => DataValidationValues.Custom,
_ => DataValidationValues.List,
};
}
// ═══════════════════════════════════════════════════