문서 repair guide와 목적형 HTML/XLSX 경로를 추가 고도화

- ArtifactRepairGuideService를 추가해 HTML/XLSX/DOCX 품질 리뷰 결과를 Repair guide 형태의 실행 가능한 보정 지침으로 변환
- HtmlSkill, ExcelSkill, DocumentAssemblerTool 출력에 repair guide를 연결해 품질 점수 뒤에 후속 보완 방향을 함께 제공
- Excel dashboard sheet에 dashboard_tiles와 variance_series를 추가해 운영 리뷰형 workbook archetype을 강화
- strategy-brief-html, operating-review-xlsx 번들 스킬을 추가해 목적형 문서 생성 진입점을 확장
- README.md와 docs/DEVELOPMENT.md에 2026-04-14 23:58 (KST) 기준 작업 이력과 검증 명령을 반영

검증 결과
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_doc_next6\\ -p:IntermediateOutputPath=obj\\verify_doc_next6\\ : 경고 0 / 오류 0
- dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter ArtifactQualityReviewServiceTests|ArtifactRepairGuideServiceTests|ExcelSkillDashboardSummaryTests|HtmlSkillConsultingSectionsTests|HtmlSkillPrintFrameTests|DocumentAssemblerStyleMapTests|DocumentAssemblerDocxFeaturesTests|DocumentAssemblerSemanticTests|PptxSkillGoldenDeckTests|DeckQualityReviewServiceTests -p:OutputPath=bin\\verify_doc_next6_tests\\ -p:IntermediateOutputPath=obj\\verify_doc_next6_tests\\ : 통과 17
This commit is contained in:
2026-04-14 23:59:09 +09:00
parent e1f6caf11a
commit 59ec4a1371
11 changed files with 281 additions and 5 deletions

View File

@@ -0,0 +1,83 @@
namespace AxCopilot.Services.Agent;
public static class ArtifactRepairGuideService
{
public static string BuildGuide(ArtifactQualityReport review)
{
if (review.Issues.Count == 0)
return "Repair guide: none";
var actions = new List<string>();
foreach (var issue in review.Issues)
{
var action = review.ArtifactType switch
{
"html" => BuildHtmlAction(issue.Message),
"xlsx" => BuildWorkbookAction(issue.Message),
"docx" => BuildDocumentAction(issue.Message),
_ => null
};
if (!string.IsNullOrWhiteSpace(action) && !actions.Contains(action, StringComparer.OrdinalIgnoreCase))
actions.Add(action);
if (actions.Count >= 3)
break;
}
if (actions.Count == 0)
return "Repair guide: review low-signal issues and tighten structure before export";
return "Repair guide: " + string.Join(" | ", actions);
}
private static string? BuildHtmlAction(string message)
{
if (message.Contains("decision summary or evidence cards", StringComparison.OrdinalIgnoreCase))
return "Add a decision summary and evidence cards near the recommendation section";
if (message.Contains("cover page", StringComparison.OrdinalIgnoreCase))
return "Add a cover page for print-ready or board-facing reports";
if (message.Contains("comparison or roadmap", StringComparison.OrdinalIgnoreCase))
return "Add comparison or roadmap blocks to make options and sequencing explicit";
if (message.Contains("supporting paragraphs", StringComparison.OrdinalIgnoreCase))
return "Expand the core sections with supporting paragraphs and business evidence";
if (message.Contains("Structured visual blocks are limited", StringComparison.OrdinalIgnoreCase))
return "Use comparison, roadmap, matrix, KPI, or evidence blocks instead of plain text only";
if (message.Contains("Executive summary", StringComparison.OrdinalIgnoreCase))
return "Add an Executive Summary section near the top of the report";
return null;
}
private static string? BuildWorkbookAction(string message)
{
if (message.Contains("dashboard sheet", StringComparison.OrdinalIgnoreCase))
return "Add a dashboard sheet with KPI tiles, trends, and links to detail sheets";
if (message.Contains("Summary sheet could better surface", StringComparison.OrdinalIgnoreCase))
return "Promote KPIs, decisions, and highlights into the summary or dashboard sheet";
if (message.Contains("data validation", StringComparison.OrdinalIgnoreCase))
return "Add data validation rules for editable status, owner, or category columns";
if (message.Contains("Conditional formatting", StringComparison.OrdinalIgnoreCase))
return "Add conditional formatting to flag priority items, variances, or risks";
if (message.Contains("more formulas or rollups", StringComparison.OrdinalIgnoreCase))
return "Add rollup formulas or summary calculations to turn detail data into insights";
if (message.Contains("Summary sheet does not link", StringComparison.OrdinalIgnoreCase))
return "Link the summary/dashboard sheet to each detail sheet for faster navigation";
return null;
}
private static string? BuildDocumentAction(string message)
{
if (message.Contains("Executive Summary", StringComparison.OrdinalIgnoreCase))
return "Add an Executive Summary section at the start of the document";
if (message.Contains("Recommendation", StringComparison.OrdinalIgnoreCase))
return "Add a recommendation or next-step section with a clear decision ask";
if (message.Contains("Appendix", StringComparison.OrdinalIgnoreCase))
return "Add an appendix or reference section for longer documents";
if (message.Contains("mostly plain paragraphs", StringComparison.OrdinalIgnoreCase))
return "Introduce tables, lists, or callout blocks to break up dense prose";
if (message.Contains("too short", StringComparison.OrdinalIgnoreCase))
return "Deepen the supporting analysis before export";
return null;
}
}

View File

@@ -263,7 +263,7 @@ public class DocumentAssemblerTool : IAgentTool
var issues = ValidateBasic(sb.ToString());
var review = ArtifactQualityReviewService.ReviewHtml(title, sb.ToString(), !string.IsNullOrWhiteSpace(coverSubtitle), toc, printReady: true);
var reviewSummary = $" HTML 품질 리뷰: {review.Score}/100 | 강점 {review.Strengths.Count} | 이슈 {review.Issues.Count}";
var reviewSummary = $" HTML 품질 리뷰: {review.Score}/100 | 강점 {review.Strengths.Count} | 이슈 {review.Issues.Count} | {ArtifactRepairGuideService.BuildGuide(review)}";
return issues.Count > 0
? $"{reviewSummary} | 기본 검토 이슈 {issues.Count}건: {string.Join("; ", issues)}"
: reviewSummary;
@@ -546,7 +546,7 @@ public class DocumentAssemblerTool : IAgentTool
headings.Any(h => ArtifactQualityReviewService.ContainsBusinessKeyword(h, "recommendation", "proposal", "next step", "action", "권고", "실행")),
headings.Any(h => ArtifactQualityReviewService.ContainsBusinessKeyword(h, "appendix", "reference", "supplement", "부록", "참고"))));
return $" DOCX 품질 리뷰: {review.Score}/100 | 강점 {review.Strengths.Count} | 이슈 {review.Issues.Count}";
return $" DOCX 품질 리뷰: {review.Score}/100 | 강점 {review.Strengths.Count} | 이슈 {review.Issues.Count} | {ArtifactRepairGuideService.BuildGuide(review)}";
void AppendStructuredContent(string rawContent)
{

View File

@@ -223,6 +223,7 @@ public class ExcelSkill : IAgentTool
false,
false));
features += $"\n{review.ToToolSummary()}";
features += $"\n{ArtifactRepairGuideService.BuildGuide(review)}";
return ToolResult.Ok(
$"Excel 파일 생성 완료: {fullPath}\n시트: {sheetName}, 열: {colCount}, 행: {rowCount}{features}",
@@ -329,10 +330,11 @@ public class ExcelSkill : IAgentTool
hasDashboardSheet,
HasSummaryItems(summarySheet, "highlights"),
HasSummaryItems(summarySheet, "actions"),
HasSummaryItems(summarySheet, "scorecards") || HasSummaryItems(summarySheet, "cards") || HasSummaryItems(summarySheet, "kpis") || HasSummaryItems(summarySheet, "trend_series"),
HasSummaryItems(summarySheet, "scorecards") || HasSummaryItems(summarySheet, "cards") || HasSummaryItems(summarySheet, "kpis") || HasSummaryItems(summarySheet, "trend_series") || HasSummaryItems(summarySheet, "dashboard_tiles") || HasSummaryItems(summarySheet, "variance_series"),
HasStructuredSummaryContent(summarySheet, "decision_summary"),
HasSummaryItems(summarySheet, "sheet_summaries")));
features += $"\n{review.ToToolSummary()}";
features += $"\n{ArtifactRepairGuideService.BuildGuide(review)}";
return ToolResult.Ok(
$"Excel ?뚯씪 ?앹꽦 ?꾨즺: {fullPath}\n?쒗듃: {summaryName}, {sheetName}, ?? {colCount}, ?? {rowCount}{features} [?붿빟 ?쒗듃]",
@@ -473,11 +475,11 @@ public class ExcelSkill : IAgentTool
hasDashboardSheet,
HasSummaryItems(summarySheet, "highlights"),
HasSummaryItems(summarySheet, "actions"),
HasSummaryItems(summarySheet, "scorecards") || HasSummaryItems(summarySheet, "cards") || HasSummaryItems(summarySheet, "kpis") || HasSummaryItems(summarySheet, "trend_series"),
HasSummaryItems(summarySheet, "scorecards") || HasSummaryItems(summarySheet, "cards") || HasSummaryItems(summarySheet, "kpis") || HasSummaryItems(summarySheet, "trend_series") || HasSummaryItems(summarySheet, "dashboard_tiles") || HasSummaryItems(summarySheet, "variance_series"),
HasStructuredSummaryContent(summarySheet, "decision_summary"),
HasSummaryItems(summarySheet, "sheet_summaries")));
return ToolResult.Ok(
$"Excel 파일 생성 완료: {fullPath}\n시트: {totalSheets}개, 총 데이터 행: {totalRows}\n{review.ToToolSummary()}",
$"Excel 파일 생성 완료: {fullPath}\n시트: {totalSheets}개, 총 데이터 행: {totalRows}\n{review.ToToolSummary()}\n{ArtifactRepairGuideService.BuildGuide(review)}",
fullPath);
}
@@ -616,8 +618,10 @@ public class ExcelSkill : IAgentTool
rowIndex++;
AppendDecisionSummarySection(summarySheet, sheetData, merges, ref rowIndex);
AppendDashboardTileSection(summarySheet, sheetData, ref rowIndex);
AppendScorecardSection(summarySheet, sheetData, ref rowIndex);
AppendTrendSection(summarySheet, sheetData, ref rowIndex);
AppendVarianceSection(summarySheet, sheetData, ref rowIndex);
AppendKpiSection(summarySheet, sheetData, merges, ref rowIndex);
AppendSheetSummarySection(summarySheet, sheetData, ref rowIndex);
AppendSummaryTextSection(summarySheet, "highlights", "Key Highlights", sheetData, merges, ref rowIndex);
@@ -729,6 +733,36 @@ public class ExcelSkill : IAgentTool
rowIndex++;
}
private static void AppendDashboardTileSection(JsonElement summarySheet, SheetData sheetData, ref uint rowIndex)
{
if (!summarySheet.SafeTryGetProperty("dashboard_tiles", out var tiles)
|| tiles.ValueKind != JsonValueKind.Array
|| tiles.GetArrayLength() == 0)
return;
var headerRow = new Row { RowIndex = rowIndex++ };
headerRow.Append(CreateSummaryCell("A", headerRow.RowIndex!.Value, "Dashboard Tiles", 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 tileIndex = 0;
foreach (var tile in tiles.EnumerateArray())
{
var stripeStyle = tileIndex % 2 == 0 ? (uint)0 : (uint)2;
var row = new Row { RowIndex = rowIndex++ };
row.Append(CreateSummaryCell("A", row.RowIndex!.Value, tile.SafeTryGetProperty("label", out var labelEl) ? labelEl.SafeGetString() ?? string.Empty : string.Empty, 1));
row.Append(CreateSummaryCell("B", row.RowIndex!.Value, tile.SafeTryGetProperty("value", out var valueEl) ? valueEl.SafeGetString() ?? string.Empty : string.Empty, 3));
row.Append(CreateSummaryCell("C", row.RowIndex!.Value, tile.SafeTryGetProperty("status", out var statusEl) ? statusEl.SafeGetString() ?? string.Empty : string.Empty, stripeStyle));
row.Append(CreateSummaryCell("D", row.RowIndex!.Value, tile.SafeTryGetProperty("note", out var noteEl) ? noteEl.SafeGetString() ?? string.Empty : string.Empty, stripeStyle));
sheetData.Append(row);
tileIndex++;
}
rowIndex++;
}
private static void AppendSheetSummarySection(JsonElement summarySheet, SheetData sheetData, ref uint rowIndex)
{
if (!summarySheet.SafeTryGetProperty("sheet_summaries", out var sheetSummaries)
@@ -791,6 +825,38 @@ public class ExcelSkill : IAgentTool
rowIndex++;
}
private static void AppendVarianceSection(JsonElement summarySheet, SheetData sheetData, ref uint rowIndex)
{
if (!summarySheet.SafeTryGetProperty("variance_series", out var varianceSeries)
|| varianceSeries.ValueKind != JsonValueKind.Array
|| varianceSeries.GetArrayLength() == 0)
return;
var headerRow = new Row { RowIndex = rowIndex++ };
headerRow.Append(CreateSummaryCell("A", headerRow.RowIndex!.Value, "Variance Overview", 1));
headerRow.Append(CreateSummaryCell("B", headerRow.RowIndex!.Value, "Actual", 1));
headerRow.Append(CreateSummaryCell("C", headerRow.RowIndex!.Value, "Target", 1));
headerRow.Append(CreateSummaryCell("D", headerRow.RowIndex!.Value, "Variance", 1));
headerRow.Append(CreateSummaryCell("E", headerRow.RowIndex!.Value, "Status", 1));
sheetData.Append(headerRow);
var index = 0;
foreach (var item in varianceSeries.EnumerateArray())
{
var stripeStyle = index % 2 == 0 ? (uint)0 : (uint)2;
var row = new Row { RowIndex = rowIndex++ };
row.Append(CreateSummaryCell("A", row.RowIndex!.Value, item.SafeTryGetProperty("label", out var labelEl) ? labelEl.SafeGetString() ?? string.Empty : string.Empty, 1));
row.Append(CreateSummaryCell("B", row.RowIndex!.Value, item.SafeTryGetProperty("actual", out var actualEl) ? actualEl.SafeGetString() ?? actualEl.ToString() : string.Empty, 3));
row.Append(CreateSummaryCell("C", row.RowIndex!.Value, item.SafeTryGetProperty("target", out var targetEl) ? targetEl.SafeGetString() ?? targetEl.ToString() : string.Empty, stripeStyle));
row.Append(CreateSummaryCell("D", row.RowIndex!.Value, item.SafeTryGetProperty("variance", out var varianceEl) ? varianceEl.SafeGetString() ?? varianceEl.ToString() : string.Empty, stripeStyle));
row.Append(CreateSummaryCell("E", row.RowIndex!.Value, item.SafeTryGetProperty("status", out var statusEl) ? statusEl.SafeGetString() ?? string.Empty : string.Empty, stripeStyle));
sheetData.Append(row);
index++;
}
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)

View File

@@ -309,6 +309,7 @@ public class HtmlSkill : IAgentTool
usePrint,
!string.IsNullOrWhiteSpace(printHeader) || !string.IsNullOrWhiteSpace(printFooter));
featureStr += $"\n{review.ToToolSummary()}";
featureStr += $"\n{ArtifactRepairGuideService.BuildGuide(review)}";
return ToolResult.Ok(
$"HTML 문서 생성 완료: {fullPath} (디자인: {mood}{featureStr})",

View File

@@ -0,0 +1,26 @@
---
name: operating-review-xlsx
label: 운영 리뷰 워크북
description: summary, dashboard, detail 시트를 갖춘 운영 리뷰용 Excel 워크북을 생성합니다.
icon: \uE9D2
when_to_use: 월간 운영 리뷰, KPI 점검, variance 분석, 부서별 상태 추적이 필요한 Excel 워크북이 필요할 때
argument-hint: <운영 리뷰 주제와 핵심 KPI>
allowed-tools:
- folder_map
- file_read
- document_plan
- data_pivot
- excel_create
tabs: cowork
---
운영 리뷰용 Excel 워크북을 작성하세요.
작성 지침:
- `document_plan`으로 먼저 `Summary -> Dashboard -> Detail` 구조를 잡고, 필요하면 `data_pivot`으로 원본 데이터를 정리한 뒤 `excel_create`를 호출하세요.
- summary sheet에는 decision summary, highlights, actions를 포함하고 dashboard sheet에는 KPI, dashboard tiles, trend/variance overview를 포함하세요.
- detail sheet는 영역별로 나누고 summary/dashboard에서 각 시트로 바로 이동할 수 있도록 구성하세요.
- editable 컬럼이 있으면 data validation을 추가하고, 우선순위/편차/리스크가 보이는 영역에는 conditional formatting을 사용하세요.
- 결과는 단순 표가 아니라 의사결정과 운영 점검이 가능한 workbook이 되도록 구성하세요.
결과물은 작업 폴더에 저장하고, 시트 구성과 핵심 지표를 함께 요약하세요.

View File

@@ -0,0 +1,25 @@
---
name: strategy-brief-html
label: HTML 전략 브리프
description: 전략 질문, 핵심 가설, 시사점, 의사결정 항목을 담은 전략 브리프 HTML 문서를 생성합니다.
icon: \uE81E
when_to_use: 경영진 검토용 전략 브리프, 방향성 정렬 문서, 옵션 비교가 필요한 HTML 전략 문서가 필요할 때
argument-hint: <전략 질문과 핵심 가설>
allowed-tools:
- folder_map
- file_read
- document_plan
- html_create
tabs: cowork
---
HTML 전략 브리프를 작성하세요.
작성 지침:
- `html_create`를 우선 사용하고 `cover`, `toc`, `print`, `sections`를 적극 활용하세요.
- 문서는 `Executive Summary -> Strategic Question -> Core Thesis -> Implications -> Decisions -> Roadmap -> Appendix` 흐름을 기본으로 잡으세요.
- `strategy_brief`, `comparison`, `roadmap`, `evidence_cards`, `decision_summary` 블록을 상황에 맞게 조합하세요.
- 단순 개요보다 전략 질문, 판단 근거, 선택지, 정렬이 필요한 의사결정을 명확히 드러내세요.
- print-ready 문서로 바로 공유할 수 있도록 cover와 print frame을 포함하는 방향을 우선 검토하세요.
결과물은 작업 폴더에 저장하고, 문서 구조와 핵심 의사결정 포인트를 함께 요약하세요.