diff --git a/furniture-3d-system/src/server/services/bom-costing/bom-costing-ai-output-contract.ts b/furniture-3d-system/src/server/services/bom-costing/bom-costing-ai-output-contract.ts index ad530276..3cd9ccc0 100644 --- a/furniture-3d-system/src/server/services/bom-costing/bom-costing-ai-output-contract.ts +++ b/furniture-3d-system/src/server/services/bom-costing/bom-costing-ai-output-contract.ts @@ -338,8 +338,21 @@ export const aiOptionalPositiveNumberSchema = z.preprocess( z.number().finite().positive().optional(), ) +const DEFAULT_AI_CONFIDENCE = 0.5 + +function parseAiConfidence(value: unknown) { + // Confidence is advisory metadata. Some otherwise complete model responses + // omit it, so retain the usable BOM with an explicitly conservative value + // instead of discarding and re-running the entire batch. + if (value === null || value === undefined) return DEFAULT_AI_CONFIDENCE + if (typeof value === 'string' && !value.trim()) { + return DEFAULT_AI_CONFIDENCE + } + return parseAiFiniteNumber(value) +} + export const aiConfidenceSchema = z.preprocess( - parseAiFiniteNumber, + parseAiConfidence, z.number().finite().min(0).max(1), ) diff --git a/furniture-3d-system/src/server/services/bom-costing/bom-costing-multimodal-analyzer.test.ts b/furniture-3d-system/src/server/services/bom-costing/bom-costing-multimodal-analyzer.test.ts index ebb8be63..0ce541f7 100644 --- a/furniture-3d-system/src/server/services/bom-costing/bom-costing-multimodal-analyzer.test.ts +++ b/furniture-3d-system/src/server/services/bom-costing/bom-costing-multimodal-analyzer.test.ts @@ -677,6 +677,34 @@ describe('BOM 多模态分析器', () => { }) }) + it('模型省略置信度时使用保守默认值,不重跑完整 BOM 批次', async () => { + const structureGroup = analyzedStructureGroup() + const modelLine = JSON.parse( + JSON.stringify(structureGroup.bomLines[0]), + ) as Record + delete modelLine.confidence + const modelEvidence = modelLine.evidence as Array> + delete modelEvidence[0]?.confidence + mockFactExtractionThenAnalysis(snapshot, { + product: { category: '书桌' }, + structureGroups: [{ + ...structureGroup, + bomLines: [modelLine, ...structureGroup.bomLines.slice(1)], + }], + skus: [analyzedSku('sku-white'), analyzedSku('sku-black')], + }) + + const result = await new BomCostingMultimodalAnalyzer().analyze({ + sourceUrl: snapshot.detailUrl, + snapshot, + }) + + expect(result.skus[0]?.bomLines[0]).toMatchObject({ + confidence: 0.5, + evidence: [{ confidence: 0.5 }], + }) + }) + it('模型返回其他不完整 SKU 信息时不暴露内部校验细节', async () => { mockFactExtractionThenAnalysis(snapshot, { product: { category: '书桌' },