diff --git a/ModerBox.Cli/README.md b/ModerBox.Cli/README.md index 254acac..cf4799c 100644 --- a/ModerBox.Cli/README.md +++ b/ModerBox.Cli/README.md @@ -42,7 +42,7 @@ dotnet run -- cd --source "C:\data" --target "C:\result.csv" # 题库转换 dotnet run -- qb --source "input.txt" --target "output.xlsx" --source-format Txt --target-format Mtb -# 题库合并(多个源文件自动检测格式并去重,支持 .xlsx/.xls 输入) +# 题库合并(多个源文件自动检测格式并去重,支持风控平台等 .xlsx/.xls 输入) dotnet run -- qb merge --sources "bank1.txt" "bank2.xlsx" "bank3.xls" --target "merged.xlsx" --target-format Mtb # 电缆走向绘制 diff --git a/ModerBox.MCP/Tools/QuestionBankTools.cs b/ModerBox.MCP/Tools/QuestionBankTools.cs index 3ecbb02..edd2794 100644 --- a/ModerBox.MCP/Tools/QuestionBankTools.cs +++ b/ModerBox.MCP/Tools/QuestionBankTools.cs @@ -12,7 +12,7 @@ public static QuestionBankConversionResult ConvertQuestionBank( [Description("Source question bank file path")] string sourcePath, [Description("Target output file path")] string targetPath, [Description("Target format (Ksb, Mtb, Wldx, Wldx4, Xiaobao, XiaobaoTxt)")] QuestionBankTargetFormat targetFormat, - [Description("Source format (AutoDetect, Txt, Wldx, Wldx4, Exc, Gdpx, Simple). If not specified, format will be auto-detected.")] QuestionBankSourceFormat? sourceFormat = null) + [Description("Source format (AutoDetect, Txt, Ksb, Mtb, Wldx, Wldx4, Exc, Gdpx, RiskControlPlatform, Simple). If not specified, format will be auto-detected.")] QuestionBankSourceFormat? sourceFormat = null) { var result = new QuestionBankConversionResult { @@ -89,7 +89,7 @@ public static QuestionBankMergeResult MergeQuestionBanks( [Description("Source question bank file paths to merge")] string[] sourcePaths, [Description("Target output file path")] string targetPath, [Description("Target format (Ksb, Mtb, Wldx, Wldx4, Xiaobao, XiaobaoTxt)")] QuestionBankTargetFormat targetFormat, - [Description("Source format (AutoDetect, Txt, Ksb, Mtb, Wldx, Wldx4, Exc, Gdpx, Simple). If not specified, each source file will be auto-detected.")] QuestionBankSourceFormat? sourceFormat = null, + [Description("Source format (AutoDetect, Txt, Ksb, Mtb, Wldx, Wldx4, Exc, Gdpx, RiskControlPlatform, Simple). If not specified, each source file will be auto-detected.")] QuestionBankSourceFormat? sourceFormat = null, [Description("Remove duplicate questions while merging. Defaults to true.")] bool deduplicate = true) { var result = new QuestionBankMergeResult diff --git a/ModerBox.QuestionBank.Test/QuestionBankServiceTests.cs b/ModerBox.QuestionBank.Test/QuestionBankServiceTests.cs index 4c92b96..8748620 100644 --- a/ModerBox.QuestionBank.Test/QuestionBankServiceTests.cs +++ b/ModerBox.QuestionBank.Test/QuestionBankServiceTests.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using System.Text; +using ClosedXML.Excel; using ModerBox.QuestionBank; using NPOI.HSSF.UserModel; using NPOI.SS.UserModel; @@ -22,6 +23,7 @@ public void FormatOptionsProvider_ReturnsDisplayNamesAndDescriptionsFromAttribut Assert.AreEqual("自动检测", sourceOptions.First(o => o.Format == QuestionBankSourceFormat.AutoDetect).DisplayName); Assert.IsFalse(sourceDescriptions.Any(d => d.DisplayName == "自动检测")); Assert.IsTrue(sourceDescriptions.Any(d => d.DisplayName == "TXT 文本" && d.Detail.Contains("Word格式题库"))); + Assert.IsTrue(sourceDescriptions.Any(d => d.DisplayName == "风控平台格式题库")); Assert.IsTrue(targetOptions.Any(o => o.DisplayName == "小包搜题 TXT (.txt)")); } @@ -385,6 +387,86 @@ public void QuestionBankConversionService_DetectSourceFormat_ForLegacyXlsKsbAndM } } + [TestMethod] + public void QuestionBankConversionService_DetectSourceFormat_ForRiskControlPlatformXlsxWithXlsExtension_ReadsQuestions() + { + var service = new QuestionBankConversionService(); + var path = Path.Combine(Path.GetTempPath(), $"questionbank_risk_control_{Guid.NewGuid():N}.xls"); + + try + { + WriteRiskControlPlatformXlsx(path); + + Assert.AreEqual(QuestionBankSourceFormat.RiskControlPlatform, service.DetectSourceFormat(path)); + + var questions = service.Read(path, QuestionBankSourceFormat.AutoDetect); + + Assert.AreEqual(4, questions.Count); + Assert.AreEqual("作业前应确认?", questions[0].Topic); + Assert.AreEqual(QuestionType.SingleChoice, questions[0].TopicType); + CollectionAssert.AreEqual(new List { "A. 带电", "B. 停电" }, questions[0].Answer); + Assert.AreEqual("B", questions[0].CorrectAnswer); + Assert.AreEqual("安全生产 / 作业组织", questions[0].Chapter); + Assert.AreEqual("解析内容", questions[0].Analysis); + + Assert.AreEqual(QuestionType.MultipleChoice, questions[1].TopicType); + CollectionAssert.AreEqual(new List { "A. 计划", "B. 措施", "C. 监护" }, questions[1].Answer); + Assert.AreEqual("AC", questions[1].CorrectAnswer); + + Assert.AreEqual(QuestionType.ShortAnswer, questions[2].TopicType); + Assert.AreEqual(0, questions[2].Answer.Count); + Assert.AreEqual("管行业必须管安全、管业务必须管安全、管生产经营必须管安全。", questions[2].CorrectAnswer); + + Assert.AreEqual(QuestionType.TrueFalse, questions[3].TopicType); + Assert.AreEqual("判断解析", questions[3].Analysis); + } + finally + { + if (File.Exists(path)) File.Delete(path); + var xlsxPath = Path.ChangeExtension(path, ".xlsx"); + if (File.Exists(xlsxPath)) File.Delete(xlsxPath); + } + } + + [TestMethod] + public void QuestionBankConversionService_DetectSourceFormat_ForRiskControlPlatformLegacyXls_ReadsQuestions() + { + var service = new QuestionBankConversionService(); + var path = Path.Combine(Path.GetTempPath(), $"questionbank_risk_control_legacy_{Guid.NewGuid():N}.xls"); + + try + { + WriteLegacyXls(path, sheet => + { + FillRiskControlPlatformHeader((row, column, value) => SetCell(sheet, row - 1, column - 1, value)); + SetCell(sheet, 1, 0, "1"); + SetCell(sheet, 1, 1, "安全生产"); + SetCell(sheet, 1, 2, "\\"); + SetCell(sheet, 1, 3, "通用题库"); + SetCell(sheet, 1, 4, "判断题"); + SetCell(sheet, 1, 5, "工作前应开展风险辨识。( )"); + SetCell(sheet, 1, 6, "A-正确|B-错误"); + SetCell(sheet, 1, 7, "A"); + SetCell(sheet, 1, 12, "\\"); + SetCell(sheet, 1, 13, "判断题解析"); + }); + + Assert.AreEqual(QuestionBankSourceFormat.RiskControlPlatform, service.DetectSourceFormat(path)); + + var questions = service.Read(path, QuestionBankSourceFormat.AutoDetect); + + Assert.AreEqual(1, questions.Count); + Assert.AreEqual(QuestionType.TrueFalse, questions[0].TopicType); + CollectionAssert.AreEqual(new List { "A. 正确", "B. 错误" }, questions[0].Answer); + Assert.AreEqual("A", questions[0].CorrectAnswer); + Assert.AreEqual("判断题解析", questions[0].Analysis); + } + finally + { + if (File.Exists(path)) File.Delete(path); + } + } + private static void WriteLegacyXls(string path, Action fillSheet) { using var workbook = new HSSFWorkbook(); @@ -400,4 +482,76 @@ private static void SetCell(ISheet sheet, int rowIndex, int columnIndex, string var row = sheet.GetRow(rowIndex) ?? sheet.CreateRow(rowIndex); row.CreateCell(columnIndex).SetCellValue(value); } + + private static void WriteRiskControlPlatformXlsx(string pathWithXlsExtension) + { + var xlsxPath = Path.ChangeExtension(pathWithXlsExtension, ".xlsx"); + using (var workbook = new XLWorkbook()) + { + var sheet = workbook.Worksheets.Add("题库格式"); + FillRiskControlPlatformHeader((row, column, value) => sheet.Cell(row, column).Value = value); + + SetCell(sheet, 2, 1, "1"); + SetCell(sheet, 2, 2, "安全生产"); + SetCell(sheet, 2, 3, "作业组织"); + SetCell(sheet, 2, 4, "通用题库"); + SetCell(sheet, 2, 5, "单选题"); + SetCell(sheet, 2, 6, "作业前应确认?"); + SetCell(sheet, 2, 7, "A-带电|B-停电"); + SetCell(sheet, 2, 8, "B"); + SetCell(sheet, 2, 13, "解析内容"); + + SetCell(sheet, 3, 1, "2"); + SetCell(sheet, 3, 2, "安全生产"); + SetCell(sheet, 3, 3, "\\"); + SetCell(sheet, 3, 4, "通用题库"); + SetCell(sheet, 3, 5, "多选题"); + SetCell(sheet, 3, 6, "风险管控应包含哪些内容?"); + SetCell(sheet, 3, 7, "A-计划|B-措施|C-监护"); + SetCell(sheet, 3, 8, "AC"); + + SetCell(sheet, 4, 1, "3"); + SetCell(sheet, 4, 2, "安全生产"); + SetCell(sheet, 4, 3, "\\"); + SetCell(sheet, 4, 4, "通用题库"); + SetCell(sheet, 4, 5, "简答题"); + SetCell(sheet, 4, 6, "安全生产“三管三必须”指的是什么?"); + SetCell(sheet, 4, 7, "\\"); + SetCell(sheet, 4, 8, "管行业必须管安全、管业务必须管安全、管生产经营必须管安全。"); + + SetCell(sheet, 5, 1, "4"); + SetCell(sheet, 5, 2, "安全生产"); + SetCell(sheet, 5, 3, "\\"); + SetCell(sheet, 5, 4, "通用题库"); + SetCell(sheet, 5, 5, "判断题"); + SetCell(sheet, 5, 6, "工作前应开展风险辨识。( )"); + SetCell(sheet, 5, 7, "A-正确|B-错误"); + SetCell(sheet, 5, 8, "A"); + SetCell(sheet, 5, 14, "判断解析"); + + workbook.SaveAs(xlsxPath); + } + + File.Copy(xlsxPath, pathWithXlsExtension, overwrite: true); + File.Delete(xlsxPath); + } + + private static void FillRiskControlPlatformHeader(Action setCell) + { + var headers = new[] + { + "序号", "一级纲要", "二级纲要", "题目分类", "题型", "题干", "选项", + "答案", "题目依据", "试题分数", "试题编码", "备注", "说明", "判断题解析" + }; + + for (var i = 0; i < headers.Length; i++) + { + setCell(1, i + 1, headers[i]); + } + } + + private static void SetCell(IXLWorksheet sheet, int rowNumber, int columnNumber, string value) + { + sheet.Cell(rowNumber, columnNumber).Value = value; + } } diff --git a/ModerBox.QuestionBank/Formats/Excel/ExcelReadCommon.cs b/ModerBox.QuestionBank/Formats/Excel/ExcelReadCommon.cs index 7f1c953..d49261a 100644 --- a/ModerBox.QuestionBank/Formats/Excel/ExcelReadCommon.cs +++ b/ModerBox.QuestionBank/Formats/Excel/ExcelReadCommon.cs @@ -12,6 +12,7 @@ public static QuestionType ParseQuestionType(string typeString) { if (s.Contains("单选")) return QuestionType.SingleChoice; if (s.Contains("多选")) return QuestionType.MultipleChoice; if (s.Contains("判断")) return QuestionType.TrueFalse; + if (s.Contains("简答")) return QuestionType.ShortAnswer; return QuestionType.SingleChoice; } diff --git a/ModerBox.QuestionBank/Formats/Excel/LegacyExcelReader.cs b/ModerBox.QuestionBank/Formats/Excel/LegacyExcelReader.cs index cc9fc74..7b37ec8 100644 --- a/ModerBox.QuestionBank/Formats/Excel/LegacyExcelReader.cs +++ b/ModerBox.QuestionBank/Formats/Excel/LegacyExcelReader.cs @@ -173,6 +173,10 @@ public static bool IsSimpleFormat(string filePath) { public static QuestionBankSourceFormat DetectFormat(string filePath) { var worksheet = ReadFirstWorksheet(filePath); + if (RiskControlPlatformReader.IsMatchingWorksheet(worksheet)) { + return QuestionBankSourceFormat.RiskControlPlatform; + } + if (IsKsbFormat(worksheet)) { return QuestionBankSourceFormat.Ksb; } diff --git a/ModerBox.QuestionBank/Formats/Ksb/KsbReader.cs b/ModerBox.QuestionBank/Formats/Ksb/KsbReader.cs index 226ed9f..6395de5 100644 --- a/ModerBox.QuestionBank/Formats/Ksb/KsbReader.cs +++ b/ModerBox.QuestionBank/Formats/Ksb/KsbReader.cs @@ -56,6 +56,7 @@ public static List ReadFromFile(string filePath) { private static QuestionType ParseQuestionType(string typeText) { if (typeText.Contains("多选")) return QuestionType.MultipleChoice; if (typeText.Contains("判断")) return QuestionType.TrueFalse; + if (typeText.Contains("简答")) return QuestionType.ShortAnswer; return QuestionType.SingleChoice; } } diff --git a/ModerBox.QuestionBank/Formats/Ksb/KsbWriter.cs b/ModerBox.QuestionBank/Formats/Ksb/KsbWriter.cs index 3ad55c9..2007973 100644 --- a/ModerBox.QuestionBank/Formats/Ksb/KsbWriter.cs +++ b/ModerBox.QuestionBank/Formats/Ksb/KsbWriter.cs @@ -53,6 +53,7 @@ private static string QuestionTypeToString(QuestionType type) { QuestionType.SingleChoice => "单选题", QuestionType.MultipleChoice => "多选题", QuestionType.TrueFalse => "判断题", + QuestionType.ShortAnswer => "简答题", _ => "单选题" }; } diff --git a/ModerBox.QuestionBank/Formats/Mtb/MtbReader.cs b/ModerBox.QuestionBank/Formats/Mtb/MtbReader.cs index 08262b6..c8409b0 100644 --- a/ModerBox.QuestionBank/Formats/Mtb/MtbReader.cs +++ b/ModerBox.QuestionBank/Formats/Mtb/MtbReader.cs @@ -52,6 +52,7 @@ public static List ReadFromFile(string filePath) { private static QuestionType ParseQuestionType(string typeText) { if (typeText.Contains("多选")) return QuestionType.MultipleChoice; if (typeText.Contains("判断")) return QuestionType.TrueFalse; + if (typeText.Contains("简答")) return QuestionType.ShortAnswer; return QuestionType.SingleChoice; } } diff --git a/ModerBox.QuestionBank/Formats/Mtb/MtbWriter.cs b/ModerBox.QuestionBank/Formats/Mtb/MtbWriter.cs index 24c7770..e36796a 100644 --- a/ModerBox.QuestionBank/Formats/Mtb/MtbWriter.cs +++ b/ModerBox.QuestionBank/Formats/Mtb/MtbWriter.cs @@ -65,6 +65,7 @@ private static string QuestionTypeToString(QuestionType type) { QuestionType.SingleChoice => "单选题", QuestionType.MultipleChoice => "多选题", QuestionType.TrueFalse => "判断题", + QuestionType.ShortAnswer => "简答题", _ => "单选题" }; } diff --git a/ModerBox.QuestionBank/Formats/RiskControl/RiskControlPlatformReader.cs b/ModerBox.QuestionBank/Formats/RiskControl/RiskControlPlatformReader.cs new file mode 100644 index 0000000..cf25870 --- /dev/null +++ b/ModerBox.QuestionBank/Formats/RiskControl/RiskControlPlatformReader.cs @@ -0,0 +1,148 @@ +using ClosedXML.Excel; +using System.Text.RegularExpressions; + +namespace ModerBox.QuestionBank; + +/// +/// 风控平台格式题库读取器。 +/// +public static class RiskControlPlatformReader { + private static readonly string[] ExpectedHeaders = { + "序号", "一级纲要", "二级纲要", "题目分类", "题型", "题干", "选项", "答案" + }; + + public static List ReadFromFile(string filePath) { + if (LegacyExcelWorkbookReader.IsLegacyExcel(filePath)) { + return LegacyExcelWorkbookReader.ReadWorksheets(filePath) + .Where(IsMatchingWorksheet) + .SelectMany(ReadFromWorksheet) + .ToList(); + } + + using var workbook = new XLWorkbook(filePath); + return workbook.Worksheets + .Where(IsMatchingWorksheet) + .SelectMany(ReadFromWorksheet) + .ToList(); + } + + public static bool IsMatchingFormat(string filePath) { + if (LegacyExcelWorkbookReader.IsLegacyExcel(filePath)) { + return LegacyExcelWorkbookReader.ReadWorksheets(filePath).Any(IsMatchingWorksheet); + } + + try { + using var workbook = new XLWorkbook(filePath); + return workbook.Worksheets.Any(IsMatchingWorksheet); + } catch { + return false; + } + } + + internal static bool IsMatchingWorksheet(LegacyExcelWorksheet worksheet) { + return IsMatchingHeader((row, column) => worksheet.GetString(row, column)); + } + + private static bool IsMatchingWorksheet(IXLWorksheet worksheet) { + return IsMatchingHeader((row, column) => worksheet.Cell(row, column).GetString()); + } + + private static bool IsMatchingHeader(Func getString) { + for (var column = 1; column <= ExpectedHeaders.Length; column++) { + if (NormalizeHeader(getString(1, column)) != ExpectedHeaders[column - 1]) { + return false; + } + } + + return true; + } + + private static IEnumerable ReadFromWorksheet(LegacyExcelWorksheet worksheet) { + return ReadRows((row, column) => worksheet.GetString(row, column), worksheet.LastRowNumber); + } + + private static IEnumerable ReadFromWorksheet(IXLWorksheet worksheet) { + var lastRow = worksheet.LastRowUsed()?.RowNumber() ?? 0; + return ReadRows((row, column) => worksheet.Cell(row, column).GetString(), lastRow); + } + + private static IEnumerable ReadRows(Func getString, int lastRow) { + for (var row = 2; row <= lastRow; row++) { + var topic = CleanPlatformCell(getString(row, 6)); + if (string.IsNullOrWhiteSpace(topic)) { + continue; + } + + var topicType = ExcelReadCommon.ParseQuestionType(getString(row, 5)); + yield return new Question { + Topic = topic, + TopicType = topicType, + Answer = ParseOptions(getString(row, 7)), + CorrectAnswer = NormalizeCorrectAnswer(getString(row, 8), topicType), + Analysis = BuildAnalysis(getString(row, 13), getString(row, 14)), + Chapter = BuildChapter(getString(row, 2), getString(row, 3)) + }; + } + } + + private static List ParseOptions(string optionsString) { + var normalized = CleanPlatformCell(optionsString); + if (string.IsNullOrWhiteSpace(normalized)) { + return new List(); + } + + return normalized + .Replace("|", "|") + .Split('|', StringSplitOptions.RemoveEmptyEntries) + .Select(NormalizeOption) + .Where(option => !string.IsNullOrWhiteSpace(option)) + .ToList(); + } + + private static string NormalizeOption(string option) { + var cleaned = CleanPlatformCell(option); + var match = Regex.Match(cleaned, @"^([A-Ha-h])\s*[-\.、.]\s*(.+)$"); + if (!match.Success) { + return cleaned; + } + + return $"{char.ToUpperInvariant(match.Groups[1].Value[0])}. {match.Groups[2].Value.Trim()}"; + } + + private static string NormalizeCorrectAnswer(string answer, QuestionType topicType) { + var cleaned = CleanPlatformCell(answer); + return topicType == QuestionType.ShortAnswer + ? cleaned + : ExcelReadCommon.NormalizeAnswer(cleaned); + } + + private static string? BuildAnalysis(string explanation, string trueFalseAnalysis) { + var parts = new[] { explanation, trueFalseAnalysis } + .Select(CleanPlatformCell) + .Where(value => !string.IsNullOrWhiteSpace(value)) + .Distinct() + .ToList(); + + return parts.Count == 0 ? null : string.Join(Environment.NewLine, parts); + } + + private static string? BuildChapter(string firstLevel, string secondLevel) { + var parts = new[] { firstLevel, secondLevel } + .Select(CleanPlatformCell) + .Where(value => !string.IsNullOrWhiteSpace(value)) + .ToList(); + + return parts.Count == 0 ? null : string.Join(" / ", parts); + } + + private static string CleanPlatformCell(string value) { + var cleaned = ExcelReadCommon.CleanCellString(value); + return cleaned == "\\" ? string.Empty : cleaned; + } + + private static string NormalizeHeader(string value) { + return new string((value ?? string.Empty) + .Where(c => !char.IsWhiteSpace(c)) + .ToArray()); + } +} diff --git a/ModerBox.QuestionBank/Formats/Wldx/WldxExcelWriter.cs b/ModerBox.QuestionBank/Formats/Wldx/WldxExcelWriter.cs index 733f9c0..a1bbaca 100644 --- a/ModerBox.QuestionBank/Formats/Wldx/WldxExcelWriter.cs +++ b/ModerBox.QuestionBank/Formats/Wldx/WldxExcelWriter.cs @@ -50,6 +50,7 @@ private static string QuestionTypeToString(QuestionType type) { QuestionType.SingleChoice => "单选题", QuestionType.MultipleChoice => "多选题", QuestionType.TrueFalse => "判断题", + QuestionType.ShortAnswer => "简答题", _ => "单选题" }; } diff --git a/ModerBox.QuestionBank/Formats/Wldx4/Wldx4ExcelWriter.cs b/ModerBox.QuestionBank/Formats/Wldx4/Wldx4ExcelWriter.cs index afa0786..eb710ae 100644 --- a/ModerBox.QuestionBank/Formats/Wldx4/Wldx4ExcelWriter.cs +++ b/ModerBox.QuestionBank/Formats/Wldx4/Wldx4ExcelWriter.cs @@ -50,6 +50,7 @@ private static string QuestionTypeToString(QuestionType type) { QuestionType.SingleChoice => "单选题", QuestionType.MultipleChoice => "多选题", QuestionType.TrueFalse => "判断题", + QuestionType.ShortAnswer => "简答题", _ => "单选题" }; } diff --git a/ModerBox.QuestionBank/Models/Question.cs b/ModerBox.QuestionBank/Models/Question.cs index ca137cf..eda652f 100644 --- a/ModerBox.QuestionBank/Models/Question.cs +++ b/ModerBox.QuestionBank/Models/Question.cs @@ -15,7 +15,11 @@ public enum QuestionType { /// /// 判断题 /// - TrueFalse + TrueFalse, + /// + /// 简答题 + /// + ShortAnswer } /// diff --git a/ModerBox.QuestionBank/README.md b/ModerBox.QuestionBank/README.md index b998f7d..6c73a0a 100644 --- a/ModerBox.QuestionBank/README.md +++ b/ModerBox.QuestionBank/README.md @@ -48,6 +48,12 @@ 8. **国电培训 JSON** - 国电培训系统导出的JSON题库格式 +9. **风控平台格式题库** + - 风控平台导出的Excel题库格式 + - 表头:序号、一级纲要、二级纲要、题目分类、题型、题干、选项、答案等 + - 选项列支持 `A-选项1|B-选项2` 格式 + - 支持 `.xlsx`、`.xls` 输入 + ### 目标格式(输出) 1. **考试宝格式** diff --git a/ModerBox.QuestionBank/Services/LlmAnalysisService.cs b/ModerBox.QuestionBank/Services/LlmAnalysisService.cs index 05eafbc..cf28ea2 100644 --- a/ModerBox.QuestionBank/Services/LlmAnalysisService.cs +++ b/ModerBox.QuestionBank/Services/LlmAnalysisService.cs @@ -267,6 +267,7 @@ private static string BuildPrompt(Question question) { QuestionType.SingleChoice => "单选题", QuestionType.MultipleChoice => "多选题", QuestionType.TrueFalse => "判断题", + QuestionType.ShortAnswer => "简答题", _ => "未知" }; diff --git a/ModerBox.QuestionBank/Services/QuestionBankConversionService.cs b/ModerBox.QuestionBank/Services/QuestionBankConversionService.cs index 8e5f8c7..340bec0 100644 --- a/ModerBox.QuestionBank/Services/QuestionBankConversionService.cs +++ b/ModerBox.QuestionBank/Services/QuestionBankConversionService.cs @@ -50,6 +50,10 @@ public enum QuestionBankSourceFormat { [FormatDetail("国电培训系统导出的JSON格式题库")] Gdpx, + [Description("风控平台格式题库")] + [FormatDetail("风控平台导出的Excel题库格式(A序号,B一级纲要,C二级纲要,E题型,F题干,G选项,H答案)")] + RiskControlPlatform, + [Description("简单 Excel")] [FormatDetail("简单5列格式(A专业,B题型,C题目,D选项,E正确答案);D列选项用逗号分隔,格式如 A. 选项1,B. 选项2;E列答案可写 A. 选项1 或 A. 选项1,C. 选项3,系统仅提取字母答案")] Simple @@ -145,6 +149,7 @@ public List Read(string filePath, QuestionBankSourceFormat format) { QuestionBankSourceFormat.Wldx4 => ExcelReader.ReadWLDX4Format(filePath), QuestionBankSourceFormat.Exc => ExcelReader.ReadEXCFormat(filePath), QuestionBankSourceFormat.Gdpx => GdpxReader.ReadFromFile(filePath), + QuestionBankSourceFormat.RiskControlPlatform => RiskControlPlatformReader.ReadFromFile(filePath), QuestionBankSourceFormat.Simple => ExcelReader.ReadSimpleFormat(filePath), _ => throw new NotSupportedException($"暂不支持的读取格式: {format}") }; @@ -301,6 +306,10 @@ private static QuestionBankSourceFormat DetectExcelFormat(string filePath) { return QuestionBankSourceFormat.Mtb; } + if (RiskControlPlatformReader.IsMatchingFormat(filePath)) { + return QuestionBankSourceFormat.RiskControlPlatform; + } + // 优先检测 Simple 格式(专业、题型、题目、选项、正确答案) if (SimpleExcelReader.IsMatchingFormat(filePath)) { return QuestionBankSourceFormat.Simple; diff --git "a/ModerBox.QuestionBank/\344\275\277\347\224\250\346\214\207\345\215\227.md" "b/ModerBox.QuestionBank/\344\275\277\347\224\250\346\214\207\345\215\227.md" index ba5ce01..667bf97 100644 --- "a/ModerBox.QuestionBank/\344\275\277\347\224\250\346\214\207\345\215\227.md" +++ "b/ModerBox.QuestionBank/\344\275\277\347\224\250\346\214\207\345\215\227.md" @@ -89,6 +89,7 @@ ModerBox/ | 网络大学4列 | 简化格式 | .xlsx, .xls | | EXC格式 | 特定格式 | .xlsx, .xls | | 简单 Excel | 5列(专业、题型、题目、选项、正确答案) | .xlsx, .xls | +| 风控平台格式题库 | 风控平台导出的题库格式 | .xlsx, .xls | | 国电培训 JSON | 国电培训系统导出的JSON | .json | ### 输出格式