From 050aa679be784d397c90b687afcb9163ede00249 Mon Sep 17 00:00:00 2001 From: wren <“porlong@qq.com”> Date: Thu, 7 May 2026 18:58:55 +0800 Subject: [PATCH] fix: stabilize rule editor yaml roundtrip --- app/utils/rules-config-editor.ts | 46 +++++++++++++++++++++++++++-- app/utils/rules-yaml-mock.server.ts | 6 ++-- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/app/utils/rules-config-editor.ts b/app/utils/rules-config-editor.ts index 82c4b39..3e363ed 100644 --- a/app/utils/rules-config-editor.ts +++ b/app/utils/rules-config-editor.ts @@ -330,7 +330,7 @@ function normalizeBooleanText(value: string | boolean | undefined): boolean { } function rewriteExtractNodes(fields: ExtractFieldSummary[]): Array> { - const topLevelFields = fields.filter((field) => !field.name.includes('[*].')); + const topLevelFields = fields.filter((field) => field.group !== '派生字段' && !field.name.includes('[*].')); return Array.from(groupBy(topLevelFields, (field) => field.group || '未分组').entries()).map(([group, items]) => ({ group, fields: items.map((field) => ({ @@ -343,6 +343,46 @@ function rewriteExtractNodes(fields: ExtractFieldSummary[]): Array> { + const derivedFields = fields.filter((field) => field.group === '派生字段'); + if (derivedFields.length === 0) { + return []; + } + + const existingMap = new Map>(); + if (Array.isArray(existingNodes)) { + existingNodes.forEach((node) => { + if (!node || typeof node !== 'object') return; + const record = deepClone(node as Record); + const name = String(record.name || '').trim(); + if (name) { + existingMap.set(name, record); + } + }); + } + + return derivedFields.map((field) => { + const existing = existingMap.get(field.name) || {}; + const nextNode: Record = { + ...existing, + name: field.name, + type: field.type || String(existing.type || 'computed'), + }; + const nextCompute = field.description && field.description !== '由其他字段计算得出' + ? field.description + : String(existing.compute || '').trim(); + if (nextCompute) { + nextNode.compute = nextCompute; + } else { + delete nextNode.compute; + } + return nextNode; + }); +} + function rewriteSubDocumentNodes(subDocuments: SubDocumentSummary[]): Array> { return subDocuments.map((document) => ({ id: document.id, @@ -474,7 +514,6 @@ function rewriteRuleNode(baseRule: Record | undefined, rule: Ru } delete nextRule.rules; - delete nextRule.logic; const existingStages = Array.isArray(nextRule.stages) ? (nextRule.stages as Array>) : []; const stages = existingStages.length > 0 ? deepClone(existingStages) : (buildMinimalRuleNode(rule).stages as Array>); @@ -494,6 +533,8 @@ function rewriteRuleNode(baseRule: Record | undefined, rule: Ru } nextRule.stages = stages; + if (rule.logic?.trim()) nextRule.logic = rule.logic.trim(); + else if (typeof nextRule.logic === 'string' && !String(nextRule.logic).trim()) delete nextRule.logic; return nextRule; } @@ -512,6 +553,7 @@ export function serializeEditableRuleConfig(config: EditableRuleConfig): string if (Array.isArray(config.metadata.inheritsFrom) && config.metadata.inheritsFrom.length > 0) metadata.inherits_from = [...config.metadata.inheritsFrom]; root.metadata = metadata; root.extract = rewriteExtractNodes(config.fields); + root.derived_fields = rewriteDerivedFieldNodes(config.fields, root.derived_fields); root.sub_documents = rewriteSubDocumentNodes(config.subDocuments); root.visual_elements = rewriteVisualElementNodes(config.visualElements); diff --git a/app/utils/rules-yaml-mock.server.ts b/app/utils/rules-yaml-mock.server.ts index 12e9bc3..f12b04b 100644 --- a/app/utils/rules-yaml-mock.server.ts +++ b/app/utils/rules-yaml-mock.server.ts @@ -415,9 +415,9 @@ function parseRules(source: string): RuleSummary[] { group, risk: stripYamlValue(ruleBlock.match(/^\s+risk:\s*(.+)$/m)?.[1] || 'medium'), score: stripYamlValue(ruleBlock.match(/^\s+score:\s*(.+)$/m)?.[1] || '-'), - type: stripYamlValue(ruleBlock.match(/^\s+type:\s*(.+)$/m)?.[1] || 'deterministic'), + type: stripYamlValue(ruleBlock.match(/^\s{4}type:\s*(.+)$/m)?.[1] || 'deterministic'), checkTypes, - logic: stripYamlValue(ruleBlock.match(/^\s+logic:\s*(.+)$/m)?.[1] || ''), + logic: stripYamlValue(ruleBlock.match(/^\s{4}logic:\s*(.+)$/m)?.[1] || ''), subRules, subRuleIds: readList(ruleBlock, 'rules'), scope: scope.slice(0, 8), @@ -425,7 +425,7 @@ function parseRules(source: string): RuleSummary[] { stageCount: subRules.length, appliesIn: readFlexibleList(ruleBlock, 'applies_in'), prompt: prompts.join('\n\n'), - description: stripYamlValue(ruleBlock.match(/^\s+desc:\s*(.+)$/m)?.[1] || '') + description: stripYamlValue(ruleBlock.match(/^\s{4}desc:\s*(.+)$/m)?.[1] || '') }; }); });