?? ?? ?? ???? PPTX ??? ? ?? ??

?? ?? ??? PPTX/DOCX/XLSX/HTML ???? ? ?????, PPTX? ???? ??? ? ???? ?? ???? ? ??? ?? ?? ???? ????.

?? ????:
- ExcelSkill? conditional_formats? ??? ?? ???? ??? ? ?????? OpenXML? ?? ???? workbook quality review? ??
- DocxSkill? style_map? ??? ???? ??/??/?? ???? ?? ?? ParagraphStyleId? ??
- HtmlSkill? print_header/print_footer ?? ?? ???? ???? ArtifactQualityReviewService? ?? ?? ?? ???? ??
- PptxTemplatePackRegistry? PptxSkill template_pack ????? ??? strategy/board/pmo/finance/sales/operating_model ??? ??? ?? ?? ?? ??? ??
- ?????, ????, ?? ???, ??? ?? ?? ?? ???? ???? ?? ?? ?? ???? ???? ???? ??

?? ??:
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_next_doc_ppt\\ -p:IntermediateOutputPath=obj\\verify_next_doc_ppt\\ => ?? 0 / ?? 0
- dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ArtifactQualityReviewServiceTests|ExcelSkillDataValidationTests|ExcelSkillConditionalFormattingTests|ExcelSkillExecutiveSummaryLinkTests|ExcelSkillSummarySheetTests|DocxSkillTemplateFeaturesTests|DocxSkillStyleMapTests|HtmlSkillConsultingSectionsTests|HtmlSkillPrintFrameTests|DocumentAssemblerDocxFeaturesTests|PptxSkillConsultingDeckTests|PptxSkillAutoRepairTests|PptxSkillTemplatePackTests" -p:OutputPath=bin\\verify_next_doc_ppt_tests\\ -p:IntermediateOutputPath=obj\\verify_next_doc_ppt_tests\\ => ?? 15
This commit is contained in:
2026-04-14 22:54:24 +09:00
parent 5607f6391e
commit 3232db1b12
14 changed files with 790 additions and 97 deletions

View File

@@ -20,15 +20,19 @@ public sealed record ArtifactQualityReport(
public string ToToolSummary()
{
var strengths = Strengths.Take(3).ToList();
var issues = Issues.OrderByDescending(i => i.Severity).Take(3).Select(i => i.Message).ToList();
var issues = Issues
.OrderByDescending(issue => issue.Severity)
.Take(3)
.Select(issue => issue.Message)
.ToList();
var parts = new List<string> { $"품질 점수 {Score}/100" };
var parts = new List<string> { $"Quality score {Score}/100" };
if (strengths.Count > 0)
parts.Add("강점: " + string.Join(", ", strengths));
parts.Add("Strengths: " + string.Join(", ", strengths));
if (issues.Count > 0)
parts.Add("보완: " + string.Join(", ", issues));
parts.Add("Needs work: " + string.Join(", ", issues));
else
parts.Add("보완 필요 사항 없음");
parts.Add("Needs work: none");
return string.Join(" | ", parts);
}
@@ -59,17 +63,48 @@ public sealed record WorkbookReviewInput(
int FormulaCount,
int HyperlinkCount,
int DataValidationCount,
int ConditionalFormattingCount,
bool HasSummarySheet,
bool HasHighlightSection,
bool HasActionSection);
public static class ArtifactQualityReviewService
{
private static readonly string[] ExecutiveKeywords = ["executive summary", "요약", "핵심 요약", "summary"];
private static readonly string[] RecommendationKeywords = ["recommendation", "권고", "제안", "next steps", "실행 과제"];
private static readonly string[] AppendixKeywords = ["appendix", "부록", "참고", "reference"];
private static readonly string[] ExecutiveKeywords =
[
"executive summary",
"summary",
"\uC694\uC57D",
"\uD575\uC2EC \uC694\uC57D",
];
public static ArtifactQualityReport ReviewHtml(string title, string html, bool hasCover, bool hasTableOfContents, bool printReady)
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;
@@ -78,7 +113,6 @@ public static class ArtifactQualityReviewService
var issues = new List<ArtifactReviewIssue>();
var sectionCount = Regex.Matches(html, @"<h2\b", RegexOptions.IgnoreCase).Count;
var subSectionCount = Regex.Matches(html, @"<h3\b", RegexOptions.IgnoreCase).Count;
var paragraphCount = Regex.Matches(html, @"<p\b", RegexOptions.IgnoreCase).Count;
var tableCount = Regex.Matches(html, @"<table\b", RegexOptions.IgnoreCase).Count;
var calloutCount = Regex.Matches(html, @"callout-(info|warning|tip|danger)", RegexOptions.IgnoreCase).Count;
@@ -90,26 +124,27 @@ public static class ArtifactQualityReviewService
var kpiCount = Regex.Matches(html, @"kpi-card|kpi-grid|metric-card", RegexOptions.IgnoreCase).Count;
var placeholderCount = CountPlaceholders(html);
if (hasCover) strengths.Add("커버 페이지 포함");
if (hasTableOfContents) strengths.Add("목차 자동 생성");
if (printReady) strengths.Add("인쇄 최적화 CSS 포함");
if (sectionCount >= 5) strengths.Add($"핵심 섹션 {sectionCount}개 구성");
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 (sectionCount >= 5) strengths.Add($"Contains {sectionCount} major sections");
if (tableCount > 0 || comparisonCount > 0 || roadmapCount > 0 || matrixCount > 0 || decisionCount > 0 || evidenceCardCount > 0 || kpiCount > 0)
strengths.Add("표/비교/로드맵/KPI 등 구조화 블록 활용");
if (calloutCount > 0) strengths.Add("핵심 메시지 강조 블록 포함");
strengths.Add("Uses structured business blocks");
if (calloutCount > 0) strengths.Add("Uses callout blocks for emphasis");
if (html.Length < 1800)
issues.Add(new("문서 본문이 짧아 메시지와 근거 밀도가 낮을 수 있습니다", ArtifactReviewSeverity.Warning));
issues.Add(new("Body content may be too short for an executive-quality document.", ArtifactReviewSeverity.Warning));
if (sectionCount < 4)
issues.Add(new("업무 문서 기준 핵심 섹션 수가 부족합니다", ArtifactReviewSeverity.Warning));
issues.Add(new("Major section count is low for a business report.", ArtifactReviewSeverity.Warning));
if (paragraphCount < sectionCount * 2)
issues.Add(new("일부 섹션의 서술이 충분하지 않습니다", ArtifactReviewSeverity.Warning));
issues.Add(new("Several sections may need more supporting paragraphs.", ArtifactReviewSeverity.Warning));
if (tableCount + comparisonCount + roadmapCount + matrixCount + decisionCount + evidenceCardCount + kpiCount == 0)
issues.Add(new("구조화 시각 요소가 없어 보고서 완성도가 낮을 수 있습니다", ArtifactReviewSeverity.Warning));
issues.Add(new("Structured visual blocks are limited.", ArtifactReviewSeverity.Warning));
if (placeholderCount > 0)
issues.Add(new($"플레이스홀더/미완성 표현 {placeholderCount}건이 남아 있습니다", ArtifactReviewSeverity.Critical));
issues.Add(new($"Found {placeholderCount} placeholder or unfinished marker(s).", ArtifactReviewSeverity.Critical));
if (!ContainsAny(html, ExecutiveKeywords))
issues.Add(new("요약 또는 핵심 메시지 섹션이 명확하지 않습니다", ArtifactReviewSeverity.Warning));
issues.Add(new("Executive summary or summary section is missing.", ArtifactReviewSeverity.Warning));
return BuildReport("html", strengths, issues);
}
@@ -119,27 +154,27 @@ public static class ArtifactQualityReviewService
var strengths = new List<string>();
var issues = new List<ArtifactReviewIssue>();
if (input.HasTemplate) strengths.Add("템플릿 기반 스타일 상속");
if (input.HasCoverPage) strengths.Add("커버 페이지 포함");
if (input.HasTableOfContents) strengths.Add("목차 포함");
if (input.HasHeaderFooter) strengths.Add("머리글/바닥글 적용");
if (input.SectionCount >= 5) strengths.Add($"핵심 섹션 {input.SectionCount}개 구성");
if (input.TableCount > 0) strengths.Add($"테이블 {input.TableCount}개 포함");
if (input.CalloutCount + input.HighlightCount > 0) strengths.Add("강조 블록 활용");
if (input.ListCount > 0) strengths.Add("실행 항목/목록 구조 포함");
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("문서 분량이 짧아 경영 보고용 밀도가 부족할 수 있습니다", ArtifactReviewSeverity.Warning));
issues.Add(new("Body content may be too short for an executive document.", ArtifactReviewSeverity.Warning));
if (input.SectionCount < 4)
issues.Add(new("업무 문서 기준 핵심 섹션 수가 부족합니다", ArtifactReviewSeverity.Warning));
issues.Add(new("Major section count is low for a business document.", ArtifactReviewSeverity.Warning));
if (!input.HasExecutiveSummarySection)
issues.Add(new("Executive Summary 또는 요약 섹션이 없습니다", ArtifactReviewSeverity.Warning));
issues.Add(new("Executive Summary section is missing.", ArtifactReviewSeverity.Warning));
if (!input.HasRecommendationSection)
issues.Add(new("권고안 또는 다음 단계 섹션이 없습니다", ArtifactReviewSeverity.Warning));
issues.Add(new("Recommendation or next-step section is missing.", ArtifactReviewSeverity.Warning));
if (input.SectionCount >= 6 && !input.HasAppendixSection)
issues.Add(new("긴 문서인데 부록/참고 섹션이 없습니다", ArtifactReviewSeverity.Info));
issues.Add(new("Appendix or reference section is limited for a long document.", ArtifactReviewSeverity.Info));
if (input.TableCount + input.ListCount + input.CalloutCount + input.HighlightCount == 0)
issues.Add(new("표, 목록, 강조 블록이 없어 문서가 단조로울 수 있습니다", ArtifactReviewSeverity.Warning));
issues.Add(new("Document structure is mostly plain paragraphs.", ArtifactReviewSeverity.Warning));
return BuildReport("docx", strengths, issues);
}
@@ -149,22 +184,25 @@ public static class ArtifactQualityReviewService
var strengths = new List<string>();
var issues = new List<ArtifactReviewIssue>();
if (input.HasSummarySheet) strengths.Add("요약 시트 포함");
if (input.DetailSheetCount > 1) strengths.Add($"상세 시트 {input.DetailSheetCount}개 구성");
if (input.FormulaCount > 0) strengths.Add($"수식 {input.FormulaCount}개 포함");
if (input.HyperlinkCount > 0) strengths.Add("시트 간 빠른 이동 링크 포함");
if (input.DataValidationCount > 0) strengths.Add("데이터 검증 규칙 포함");
if (input.HasHighlightSection) strengths.Add("핵심 인사이트 영역 포함");
if (input.HasActionSection) strengths.Add("후속 액션 영역 포함");
if (input.HasSummarySheet) strengths.Add("Includes summary 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.SheetCount > 1 && !input.HasSummarySheet)
issues.Add(new("멀티시트 워크북인데 요약 시트가 없습니다", ArtifactReviewSeverity.Warning));
issues.Add(new("Workbook has multiple sheets but no summary sheet.", ArtifactReviewSeverity.Warning));
if (input.DataRowCount >= 10 && input.FormulaCount == 0)
issues.Add(new("데이터 행 수 대비 수식/집계가 부족합니다", ArtifactReviewSeverity.Warning));
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("요약 시트에서 상세 시트로 이동하는 링크가 없습니다", ArtifactReviewSeverity.Warning));
issues.Add(new("Summary sheet does not link to detail sheets.", ArtifactReviewSeverity.Warning));
if (input.DataValidationCount == 0 && input.DataRowCount >= 5)
issues.Add(new("입력 통제를 위한 데이터 검증 규칙이 없습니다", ArtifactReviewSeverity.Info));
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));
return BuildReport("xlsx", strengths, issues);
}
@@ -194,21 +232,17 @@ public static class ArtifactQualityReviewService
private static int CountPlaceholders(string text)
{
var count = 0;
if (string.IsNullOrWhiteSpace(text))
return count;
return 0;
var patterns = new[]
{
@"\[(todo|placeholder|내용.*작성|fill me)\]",
@"\[(todo|placeholder|fill me)\]",
@"lorem ipsum",
@"tbd",
@"\bTBD\b",
};
foreach (var pattern in patterns)
count += Regex.Matches(text, pattern, RegexOptions.IgnoreCase).Count;
return count;
return patterns.Sum(pattern => Regex.Matches(text, pattern, RegexOptions.IgnoreCase).Count);
}
private static bool ContainsAny(string text, IEnumerable<string> keywords)