const complianceChecklist = { trade: [ { item: '合同主体与身份真实', positive: ['身份证', '主体', '本人', '授权', '委托书', '营业执照'], negative: ['代签', '不露面', '身份不明'], question: '交易双方主体、身份、授权文件是否真实完整?' }, { item: '权属/处分权清晰', positive: ['产权清晰', '无抵押', '无查封', '可过户', '房东同意', '证照齐全'], negative: ['抵押', '查封', '贷款未结清', '产权不清', '无权处分'], question: '标的是否存在抵押、查封、未解押、共有权人不同意或无权处分?' }, { item: '标的描述明确', positive: ['面积', '房号', '车架号', '里程', '设备清单', '附属设施'], negative: ['模糊', '口头', '说不清'], question: '标的物位置、编号、面积/型号、附属物和交付状态是否写清?' }, { item: '价款与付款节点明确', positive: ['定金', '首付', '尾款', '监管', '付款节点', '价格'], negative: ['口头付款', '私下转账', '不写'], question: '价款、定金、首付、尾款、资金监管和付款节点是否明确?' }, { item: '交付/过户/验收节点明确', positive: ['交付', '过户', '验收', '检测', '交接', '节点'], negative: ['拖延', '不配合', '无法过户'], question: '交付、过户、验收、检测和资料移交的时间节点是否明确?' }, { item: '违约责任与解除条件明确', positive: ['违约', '赔偿', '退款', '解除', '逾期', '定金罚则'], negative: ['没违约', '不退', '口头承诺'], question: '逾期、无法过户、质量问题、资金不到位时的违约责任和解除条件是什么?' }, { item: '强制性规定/限购限贷/税费合规', positive: ['限购', '贷款', '税费', '资质', '备案', '合规'], negative: ['规避', '阴阳合同', '逃税', '借名'], question: '是否涉及限购限贷、税费、备案、资质、阴阳合同或借名交易风险?' }, { item: '证据留存与争议解决', positive: ['书面', '聊天记录', '录音', '收据', '发票', '争议解决', '管辖'], negative: ['只口头', '无凭证'], question: '关键沟通、付款凭证、验收证据、争议解决和管辖条款是否留存?' }, ], career: [ { item: '劳动/合作主体清晰', positive: ['劳动合同', '公司', '主体', '岗位'], negative: ['外包不明', '主体不清'], question: '用工主体、岗位、汇报关系是否清晰?' }, { item: '薪酬绩效书面明确', positive: ['薪酬', '绩效', '奖金', '提成', '书面'], negative: ['口头', '画饼'], question: '薪酬、奖金、绩效、提成和晋升承诺是否有书面依据?' }, { item: '工时加班与休假合规', positive: ['加班费', '调休', '工时', '休假'], negative: ['长期加班', '无加班费'], question: '工时、加班、调休、休假是否符合劳动规则和公司制度?' }, { item: '权责边界与成果归属', positive: ['职责', '权限', '成果', '归属', '授权'], negative: ['权责不清', '背锅'], question: '职责、权限、成果归属和责任边界是否明确?' }, { item: '离职/竞业/保密风险', positive: ['保密', '竞业', '离职', '交接'], negative: ['限制不清', '违约金'], question: '是否涉及保密、竞业限制、违约金、离职交接或知识产权归属?' }, ], partner: [ { item: '合作主体与授权', positive: ['合同', '主体', '授权', '公司', '个人'], negative: ['代签', '主体不清'], question: '合作主体、授权和签约身份是否明确?' }, { item: '贡献/分工/交付物明确', positive: ['分工', '交付', '节点', '贡献', '责任'], negative: ['口头', '模糊'], question: '各方贡献、分工、交付物和验收节点是否明确?' }, { item: '收益分配与结算规则明确', positive: ['分配', '结算', '比例', '账期', '透明'], negative: ['不透明', '拖款'], question: '收益分配、成本承担、结算周期和账目透明规则是否明确?' }, { item: '风险承担和违约退出机制', positive: ['违约', '退出', '赔偿', '止损', '解除'], negative: ['背锅', '无法退出'], question: '违约、亏损、责任承担、退出和清算机制是否明确?' }, { item: '知识产权/客户资源/数据归属', positive: ['知识产权', '客户', '数据', '账号', '归属'], negative: ['抢客户', '账号被卡'], question: '客户资源、账号、数据、知识产权和后续使用权归谁?' }, { item: '证据留存与争议解决', positive: ['书面', '记录', '收据', '管辖', '仲裁'], negative: ['只口头', '无凭证'], question: '关键沟通、交付证据、账目和争议解决条款是否留存?' }, ], }; function clamp(value, min = 0, max = 1) { return Math.min(max, Math.max(min, value)); } function round(value, digits = 3) { const base = 10 ** digits; return Math.round(value * base) / base; } function findEvidence(text, words) { return words.filter((word) => text.includes(word)); } function softmax(values) { const max = Math.max(...values); const exps = values.map((value) => Math.exp(value - max)); const sum = exps.reduce((total, value) => total + value, 0); return exps.map((value) => value / sum); } function weightedAverage(features, weightSelector) { const totalWeight = features.reduce((sum, item) => sum + weightSelector(item), 0); if (totalWeight === 0) return 0.5; return features.reduce((sum, item) => sum + item.value * weightSelector(item), 0) / totalWeight; } function normalizeWeights(weights) { const total = Object.values(weights).reduce((sum, value) => sum + Math.max(value, 0.0001), 0); return Object.fromEntries(Object.entries(weights).map(([key, value]) => [key, round(Math.max(value, 0.0001) / total)])); } function bucketImportance(features, predicate) { const bucket = features.filter(predicate); if (bucket.length === 0) return 0.5; return round(clamp(bucket.reduce((sum, item) => sum + item.weight * (0.55 + item.confidence * 0.45), 0) / bucket.length)); } function deriveContractFormulaWeights(features) { const textOf = (item) => featureText(item); const needImportance = bucketImportance(features, (item) => /需求|匹配|收益|benefit|matching|need|utility|价值/i.test(textOf(item))); const riskImportance = bucketImportance(features, (item) => /风险|损失|底线|下行|不确定|risk|loss|floor|uncertain|downside/i.test(textOf(item))); const controlImportance = bucketImportance(features, isControlFeature); const reliabilityImportance = bucketImportance(features, isReliabilityFeature); const referenceImportance = bucketImportance(features, (item) => /参照|公允|市场|预期|reference|market|anchor|expectation/i.test(textOf(item))); const surplusImportance = bucketImportance(features, (item) => /共同|剩余|互补|双赢|surplus|win|reciprocal|互惠/i.test(textOf(item))); return { matching: normalizeWeights({ nash: 0.32 * (0.72 + needImportance * 0.56), reference: 0.2 * (0.72 + referenceImportance * 0.56), surplus: 0.16 * (0.72 + surplusImportance * 0.56), outsideOption: 0.12, antiHoldUp: 0.1 * (0.72 + riskImportance * 0.56), control: 0.1 * (0.72 + controlImportance * 0.56), }), holdUp: normalizeWeights({ risk: 0.38 * (0.72 + riskImportance * 0.56), gap: 0.2 * (0.72 + referenceImportance * 0.56), missing: 0.16, control: 0.14 * (0.72 + controlImportance * 0.56), reliability: 0.12 * (0.72 + reliabilityImportance * 0.56), }), score: normalizeWeights({ winWin: 0.42 * (0.72 + surplusImportance * 0.56), matching: 0.24 * (0.72 + needImportance * 0.56), reference: 0.16 * (0.72 + referenceImportance * 0.56), coverage: 0.1, surplus: 0.08 * (0.72 + surplusImportance * 0.56), }), }; } function normalizeDynamicFactor(input, index) { const safeLabel = input.label || `开放参数 ${index + 1}`; const safeCategory = input.category || '开放因素'; const value = Number(input.value); const confidence = Number(input.confidence); const weight = Number(input.weight); if (!Number.isFinite(value) || !Number.isFinite(confidence) || !Number.isFinite(weight)) { throw new Error(`Invalid open factor numeric fields: ${safeLabel}`); } return { key: input.key || `open_${index + 1}_${safeLabel.slice(0, 12)}`, label: safeLabel, category: safeCategory, source: 'open', value: round(clamp(value)), confidence: round(clamp(confidence)), weight: round(clamp(weight, 0, 1)), evidence: input.evidence && input.evidence.length > 0 ? input.evidence : ['开放参数抽取器识别'], missing: false, }; } function dynamicWeight(input, kind) { if (!input) throw new Error(`Missing open factor for ${kind} weight`); const value = kind === 'user' ? input.userWeight : kind === 'other' ? input.otherWeight : kind === 'risk' ? input.riskWeight : input.refWeight; const numericValue = Number(value); if (!Number.isFinite(numericValue)) { throw new Error(`Missing ${kind} weight for open factor: ${input.label || input.key || 'unknown'}`); } return clamp(numericValue, 0, 1); } function featureText(item) { return `${item.key} ${item.label} ${item.category} ${item.evidence.join(' ')}`; } function isControlFeature(item) { return /控制权|理论2|拍板|决定权|否决权|验收权|退出权|暂停|解除|转移|托管|授权|权限|归属|账号|数据|代码|IP|知识产权|付款节点|尾款|交付|验收|职责边界|资源调度|最终决定|共同确认|control|rights|decision|veto|approval|ownership|authority|permission|handover|acceptance|payment|milestone|escrow|exit|terminate|pause/i.test(featureText(item)); } function isStateTriggerFeature(item) { return /状态|触发|逾期|不达标|未达标|验收不通过|验收失败|验收不合格|付款异常|资源不到位|融资失败|融资|现金流|资金|无法转岗|无法转AI|不能保证|不保证|职责扩大|职责泛化|延期|暂停|解除|退出|退款|退货|尾款|里程碑|cliff|vesting|回购|清算|转移|共同决策|state|trigger|contingency|delay|overdue|fail|failure|refund|return|vesting|buyback|liquidation|fallback|walkaway|funding|financing|runway|cashflow|cannot guarantee|not guaranteed/i.test(featureText(item)); } function isReliabilityFeature(item) { return /理论4|可信|履约|信任|信誉|证据|案例|书面|合同|透明|可查|第三方|发票|授权|质保|SLA|验收标准|项目经理|正规|历史|记录|资金|融资|现金流|口头|画饼|不确定|拒绝写|不透明|无案例|没案例|reliability|trust|credible|evidence|case|written|contract|transparent|verifiable|third|invoice|warranty|runway|funding|financing|verbal|uncertain|refuse/i.test(featureText(item)); } function buildControlRights(features, specs) { const controlFeatures = features.filter(isControlFeature); const controlSignalFeatures = controlFeatures.filter((item) => item.source === 'open' || !item.missing); const controlBasis = controlSignalFeatures.length > 0 ? controlSignalFeatures : controlFeatures; const controlAlignment = round(controlBasis.length > 0 ? weightedAverage(controlBasis, (item) => item.weight * (0.7 + item.confidence * 0.3)) : 0.42); const residualControlRisk = round(clamp(1 - controlAlignment)); const specificInvestment = round(weightedAverage(features.filter((item) => /专用|资产|投入|资源|客户|数据|账号|技术|装修|项目|成果|成长|品牌|股权|IP|知识产权/.test(`${item.label}${item.category}${item.evidence.join('')}`)), (item) => item.weight * (0.7 + item.confidence * 0.3))); const investmentIncentiveFit = round(clamp(0.58 * controlAlignment + 0.42 * (1 - Math.abs(specificInvestment - controlAlignment)))); const stateControlFeatures = features.filter((item) => item.key === 'state_control_transfer' || isStateTriggerFeature(item)); const stateSignalFeatures = stateControlFeatures.filter((item) => item.source === 'open' || !item.missing); const stateBasis = stateSignalFeatures.length > 0 ? stateSignalFeatures : stateControlFeatures; const stateContingencyReadiness = round(stateBasis.length > 0 ? weightedAverage(stateBasis, (item) => item.weight * (0.65 + item.confidence * 0.35)) : 0.35); const missingControlQuestions = specs .filter((spec) => spec.category.includes('控制权')) .filter((spec) => !features.some((item) => item.key === spec.key && !item.missing)) .map((spec) => spec.question); const uniqueMissingControlQuestions = [...new Set(missingControlQuestions)].slice(0, 3); const recommendedAllocation = residualControlRisk >= 0.58 ? '存在后续被锁定风险:凡涉及付款、验收、账号、客户、数据、IP、预算、方向、资源投入和责任承担的事项,都需要分项配置控制权,并设置逾期、不达标、违约或不投入时的暂停、转移、解除和退出机制。' : stateContingencyReadiness < 0.5 ? '基础控制权可谈,但状态变化机制不足:需要补充逾期、不达标、验收失败、资源不到位时的暂停、转移、解除或共同决策条款。' : '控制权配置相对可接受:继续把关键事项写成分项决策权、触发条件和证据留存。'; return { controlAlignment, residualControlRisk, investmentIncentiveFit, stateContingencyReadiness, recommendedAllocation, missingControlQuestions: uniqueMissingControlQuestions, }; } function buildBayesianReliability(text, features = []) { const prior = 0.52; const positiveSignals = [ { word: '书面', likelihood: 1.45 }, { word: '透明', likelihood: 1.35 }, { word: '可查', likelihood: 1.28 }, { word: '第三方', likelihood: 1.28 }, { word: '资金到位', likelihood: 1.38 }, { word: '贷款已预批', likelihood: 1.32 }, { word: '定金', likelihood: 1.22 }, { word: '共同确认', likelihood: 1.3 }, { word: '明确', likelihood: 1.22 }, { word: '按时', likelihood: 1.3 }, { word: '兑现', likelihood: 1.36 }, { word: '后台数据', likelihood: 1.28 }, { word: '愿意写', likelihood: 1.42 }, { word: '验收标准', likelihood: 1.24 }, { word: '违约责任', likelihood: 1.2 }, { word: 'written', likelihood: 1.42 }, { word: 'transparent', likelihood: 1.32 }, { word: 'verifiable', likelihood: 1.28 }, { word: 'third party', likelihood: 1.26 }, { word: 'preapproved', likelihood: 1.32 }, { word: 'deposit', likelihood: 1.22 }, { word: 'clear terms', likelihood: 1.3 }, { word: 'acceptance criteria', likelihood: 1.24 }, { word: 'penalty clause', likelihood: 1.2 }, ]; const negativeSignals = [ { word: '口头', likelihood: 0.66 }, { word: '画饼', likelihood: 0.58 }, { word: '不透明', likelihood: 0.52 }, { word: '拒绝写', likelihood: 0.42 }, { word: '后面再说', likelihood: 0.55 }, { word: '先签', likelihood: 0.62 }, { word: '拖延', likelihood: 0.65 }, { word: '变卦', likelihood: 0.48 }, { word: '反复', likelihood: 0.6 }, { word: '结算慢', likelihood: 0.58 }, { word: '无承诺', likelihood: 0.56 }, { word: '没有承诺', likelihood: 0.56 }, { word: '不愿提供', likelihood: 0.5 }, { word: '最终决策都由他', likelihood: 0.5 }, { word: '单方决定', likelihood: 0.48 }, { word: '账号归属没有写清', likelihood: 0.5 }, { word: 'verbal', likelihood: 0.62 }, { word: 'refuses to write', likelihood: 0.42 }, { word: 'refuse to write', likelihood: 0.42 }, { word: 'later', likelihood: 0.6 }, { word: 'not transparent', likelihood: 0.52 }, { word: 'delayed settlement', likelihood: 0.58 }, { word: 'unclear ownership', likelihood: 0.5 }, { word: 'single-sided decision', likelihood: 0.48 }, { word: 'no veto right', likelihood: 0.52 }, ]; const hasPositiveSignal = (word) => { if (!text.includes(word)) return false; if (word === '透明' && text.includes('不透明')) return false; if (word === '书面' && /拒绝.{0,4}书面|不愿.{0,4}书面/.test(text)) return false; if (text.includes(`not ${word}`) || text.includes(`no ${word}`) || text.includes(`without ${word}`)) return false; return true; }; let odds = prior / (1 - prior); const positiveEvidence = []; const negativeEvidence = []; for (const signal of positiveSignals) { if (hasPositiveSignal(signal.word)) { odds *= signal.likelihood; positiveEvidence.push(signal.word); } } for (const signal of negativeSignals) { if (text.includes(signal.word)) { odds *= signal.likelihood; negativeEvidence.push(signal.word); } } for (const item of features.filter(isReliabilityFeature)) { const strength = item.weight * (0.55 + item.confidence * 0.45); const evidenceLabel = item.evidence[0] || item.label; if (item.value >= 0.62) { odds *= 1 + (item.value - 0.5) * strength; positiveEvidence.push(evidenceLabel); } else if (item.value <= 0.42) { odds *= Math.max(0.35, 1 - (0.5 - item.value) * strength * 1.45); negativeEvidence.push(evidenceLabel); } } const posterior = round(clamp(odds / (1 + odds))); const reliabilityLevel = posterior >= 0.68 ? 'high' : posterior >= 0.45 ? 'medium' : 'low'; const interpretation = reliabilityLevel === 'high' ? '对方可信履约信号较强,可以继续推进,但仍需把关键承诺写成可验证条件。' : reliabilityLevel === 'medium' ? '对方可信履约能力尚未充分验证,适合继续谈,但必须用书面条件、验收标准和退出机制降低不确定性。' : '对方可信履约信号偏弱,不宜依赖口头承诺,应先要求书面确认、履约担保或降低投入。'; return { prior, posterior, evidenceCount: positiveEvidence.length + negativeEvidence.length, positiveEvidence, negativeEvidence, reliabilityLevel, interpretation, }; } function buildSuccessPanel(input) { const weightedSum = (terms) => terms.reduce((sum, term) => sum + term.value * term.coefficient, 0); const withContribution = (terms) => terms.map((term) => ({ ...term, value: round(term.value), contribution: round(term.value * term.coefficient) })); const sigmoid = (value) => 1 / (1 + Math.exp(-value)); const terms = [ { label: '当前匹配质量', value: input.matchingEquilibrium, weight: 0.24, direction: 1 }, { label: '双方总剩余', value: input.totalSurplus, weight: 0.16, direction: 1 }, { label: '对方可信履约', value: input.reliability, weight: 0.18, direction: 1 }, { label: '控制权配置', value: input.controlAlignment, weight: 0.14, direction: 1 }, { label: '信息完整度', value: input.coverage, weight: 0.09, direction: 1 }, { label: '替代选择压力', value: input.outsideOptionPressure, weight: 0.07, direction: 1 }, { label: '状态触发机制', value: input.stateContingencyReadiness, weight: 0.08, direction: 1 }, { label: '被卡住风险', value: input.holdUpRisk, weight: 0.16, direction: -1 }, { label: '预期缺口', value: input.expectationGap, weight: 0.11, direction: -1 }, { label: '下行损失风险', value: input.downsideProbability, weight: 0.1, direction: -1 }, ]; const positiveScore = terms .filter((term) => term.direction === 1) .reduce((sum, term) => sum + term.weight * term.value, 0); const negativeScore = terms .filter((term) => term.direction === -1) .reduce((sum, term) => sum + term.weight * term.value, 0); const successRaw = positiveScore - negativeScore; const neutralRaw = 0.38 + 0.32 * input.coverage + 0.22 * (1 - Math.abs(0.5 - input.matchingEquilibrium)) - 0.18 * Math.abs(positiveScore - negativeScore); const failureRaw = negativeScore + 0.22 * (1 - input.reliability) + 0.16 * (1 - input.controlAlignment) - positiveScore * 0.45; const [success, neutral, failure] = softmax([successRaw * 4.2, neutralRaw * 2.4, failureRaw * 4.2]).map((value) => round(value)); const bargainingFactors = [ { label: '双方总剩余', value: input.totalSurplus, coefficient: 1.05 }, { label: '当前匹配质量', value: input.matchingEquilibrium, coefficient: 1 }, { label: '替代选择压力', value: input.outsideOptionPressure, coefficient: 0.62 }, { label: '对方可信履约', value: input.reliability, coefficient: 0.54 }, { label: '信息完整度', value: input.coverage, coefficient: 0.48 }, { label: '预期缺口', value: input.expectationGap, coefficient: -0.8 }, { label: '被卡住风险', value: input.holdUpRisk, coefficient: -0.62 }, ]; const currentNegotiation = round(clamp(sigmoid(-1.05 + weightedSum(bargainingFactors)))); const concessionGainFactors = [ { label: '高价值低成本交换空间', value: input.outsideOptionPressure, coefficient: 0.38 }, { label: '预期缺口可修复度', value: 1 - input.expectationGap, coefficient: 0.32 }, { label: '状态触发机制可设计度', value: input.stateContingencyReadiness, coefficient: 0.24 }, { label: '控制权可锁定度', value: input.controlAlignment, coefficient: 0.22 }, { label: '对方可信履约', value: input.reliability, coefficient: 0.18 }, ]; const concessionCostFactors = [ { label: '下行损失风险', value: input.downsideProbability, coefficient: 0.32 }, { label: '被卡住风险', value: input.holdUpRisk, coefficient: 0.28 }, { label: '控制权不足导致的让步损耗', value: 1 - input.controlAlignment, coefficient: 0.22 }, { label: '对方可信不足导致的让步损耗', value: 1 - input.reliability, coefficient: 0.18 }, ]; const concessionGain = round(clamp(weightedSum(concessionGainFactors))); const concessionCost = round(clamp(weightedSum(concessionCostFactors))); const paretoImprovement = round(clamp(concessionGain - concessionCost + 0.5)); const afterConcession = round(clamp(currentNegotiation + 0.42 * paretoImprovement * (1 - currentNegotiation) - 0.16 * concessionCost)); const survivalFactors = [ { label: '对方可信履约', value: input.reliability, coefficient: 0.34 }, { label: '控制权配置', value: input.controlAlignment, coefficient: 0.24 }, { label: '状态触发机制', value: input.stateContingencyReadiness, coefficient: 0.22 }, { label: '信息完整度', value: input.coverage, coefficient: 0.1 }, { label: '抗下行损失能力', value: 1 - input.downsideProbability, coefficient: 0.1 }, ]; const hazardFactors = [ { label: '被卡住风险', value: input.holdUpRisk, coefficient: 0.34 }, { label: '预期缺口', value: input.expectationGap, coefficient: 0.24 }, { label: '下行损失风险', value: input.downsideProbability, coefficient: 0.22 }, { label: '可信履约不足', value: 1 - input.reliability, coefficient: 0.2 }, ]; const baseSurvival = round(clamp(weightedSum(survivalFactors))); const hazard = round(clamp(weightedSum(hazardFactors))); const fulfillment = round(clamp(baseSurvival * Math.exp(-0.72 * hazard))); const grade = success >= 0.62 && failure <= 0.22 ? 'advantage' : failure >= 0.42 ? 'danger' : 'balanced'; const positive = terms .filter((term) => term.direction === 1) .map((term) => ({ label: term.label, value: round(term.value), impact: round(term.weight * term.value) })) .sort((a, b) => b.impact - a.impact) .slice(0, 5); const negative = terms .filter((term) => term.direction === -1) .map((term) => ({ label: term.label, value: round(term.value), impact: round(term.weight * term.value) })) .sort((a, b) => b.impact - a.impact) .slice(0, 5); return { success, neutral, failure, stages: { currentNegotiation, afterConcession, fulfillment, }, stageModels: { currentNegotiation: { model: 'bargaining-scorecard', factors: withContribution(bargainingFactors), }, afterConcession: { model: 'mechanism-counterfactual', concessionGain, concessionCost, paretoImprovement, factors: [...withContribution(concessionGainFactors), ...withContribution(concessionCostFactors.map((term) => ({ ...term, coefficient: -term.coefficient })))], }, fulfillment: { model: 'bayesian-survival', baseSurvival, hazard, factors: [...withContribution(survivalFactors), ...withContribution(hazardFactors.map((term) => ({ ...term, coefficient: -term.coefficient })))], }, }, grade, formulaVersion: 'success-panel-deterministic-1.2', drivers: { positive, negative, }, }; } function buildQuestions(features, specs) { const missingKeys = features .filter((item) => item.missing) .sort((a, b) => b.weight - a.weight) .map((item) => item.key); const questions = missingKeys .map((key) => specs.find((spec) => spec.key === key)?.question) .filter((question) => Boolean(question)); return [...new Set(questions)].slice(0, 5); } function missingSpecQuestions(_features, specs, gapType) { return specs .filter((spec) => spec.gapType === gapType || (gapType === 'user-only' && spec.gapType === 'hybrid')) .sort((a, b) => b.weight - a.weight) .map((spec) => spec.question); } function hasObservedFeature(features, key) { return features.some((item) => item.key === key && !item.missing); } function buildInformationSufficiency(text, features, specs, coverage) { const openFeatures = features.filter((item) => item.source === 'open' && !item.missing); const openText = openFeatures.flatMap((item) => [item.key, item.label, item.category, ...item.evidence]).join(' '); const combinedText = `${text} ${openText}`; const hasOpenDecisionMaterial = openFeatures.length >= 3; const concreteArrangement = (text.length >= 30 || hasOpenDecisionMaterial) && /offer|报价|价格|年薪|薪酬|合作|合伙|买|卖|签|外包|入职|试用|房|车|项目|供应商|服务商|承包商|代理|渠道|平台|资源方|需求方|承诺方|交易对手|合同|岗位|公司|团队|客户|付款|交付|验收|股权|期权|分成|返点|佣金|授权|控制权|条件包|路径|安排|标的|候选|方案|A|B|C|D|对方|买家|卖家|candidate|supplier|vendor|contractor|partner|proposal|payment|delivery|acceptance|equity|option|budget|deadline|IP|data|code|control/i.test(combinedText); const exchangeTerms = /报价|价格|年薪|薪酬|付款|交付|验收|股权|期权|职责|岗位|负责|期限|现金流|融资|提成|分成|返点|佣金|预算|首付|贷款|让价|降价|万|元|资源|授权|控制权|退出|区域|账号|数据|代码|IP|客户归属|quote|price|salary|payment|delivery|acceptance|budget|deadline|runway|funding|financing|scope|responsibility|ownership|rights|exit/i.test(combinedText); const userNeedSignals = [ 'user_core_need', 'need_priority_order', 'time_horizon', 'capability_fit', 'constraint_floor', ].filter((key) => hasObservedFeature(features, key)).length; const controlSignals = hasObservedFeature(features, 'residual_control') || hasObservedFeature(features, 'state_control_transfer'); const marketSignals = hasObservedFeature(features, 'market_anchor') || /市场|行情|同类|竞品|同行|成交|挂牌|薪酬|报价|替代|备选|多家|多个|竞争|搜索补充|公开资料|公开信息|quote|price|market|alternative|competition|benchmark/i.test(combinedText); const reliabilitySignals = /书面|透明|可查|证明|案例|记录|口碑|兑现|拖延|不透明|融资|现金流|贷款|首付|履约|违约|written|contract|case|record|reliable|funding|runway|financing|breach/i.test(combinedText); const userQuestions = [...new Set(missingSpecQuestions(features, specs, 'user-only'))].slice(0, 3); const searchQuestions = [...new Set(missingSpecQuestions(features, specs, 'searchable'))].slice(0, 3); const hybridQuestions = [...new Set(missingSpecQuestions(features, specs, 'hybrid'))].slice(0, 2); const userOnlyMissing = specs .filter((spec) => spec.gapType === 'user-only' || spec.gapType === 'hybrid') .map((spec) => spec.label); const searchableMissing = specs .filter((spec) => spec.gapType === 'searchable' || spec.gapType === 'hybrid') .map((spec) => spec.label); let score = 0; if (concreteArrangement) score += 0.28; if (exchangeTerms) score += 0.16; score += Math.min(userNeedSignals, 3) * 0.12; if (marketSignals) score += 0.11; if (controlSignals) score += 0.1; if (reliabilitySignals) score += 0.08; score += Math.min(coverage, 1) * 0.15; score = round(clamp(score), 3); const reasons = [ concreteArrangement ? '已经有具体安排或候选对象' : '还没有具体安排或候选对象', exchangeTerms ? '已经有核心交换条件' : '缺少价格、薪酬、付款、交付、职责或期限等核心条件', userNeedSignals > 0 ? `已有${userNeedSignals}类用户需求/约束线索` : '缺少用户本人目标、取舍或底线', marketSignals ? '已有市场/替代选择线索,或可由 GPT 搜索补足' : '缺少市场情况或替代选择参照', controlSignals ? '已有控制权或状态变化线索' : '缺少关键事项谁说了算、出问题如何处理', ]; const blocking = !hasOpenDecisionMaterial && (!concreteArrangement || (!exchangeTerms && userNeedSignals === 0)); const level = blocking ? 'insufficient' : score >= 0.72 ? 'sufficient' : 'preliminary'; return { level, score, blocking, reasons, userOnlyMissing: [...new Set(userOnlyMissing)].slice(0, 5), searchableMissing: [...new Set(searchableMissing)].slice(0, 5), userQuestions: [...new Set([...userQuestions, ...hybridQuestions])].slice(0, 3), searchQuestions, }; } function hasComputableDecisionSignal(dynamicFeatures, coverage, metrics) { const observedOpenFeatures = dynamicFeatures.filter((item) => !item.missing); if (observedOpenFeatures.length < 2) return false; const dynamicText = observedOpenFeatures .flatMap((item) => [item.key, item.label, item.category, ...item.evidence]) .join(' '); const hasExchangeSignal = /报价|价格|年薪|薪酬|付款|交付|验收|职责|岗位|负责|期限|现金流|融资|股权|期权|分成|预算|资源|客户|代码|数据|IP|授权|控制权|退出|quote|price|salary|payment|delivery|acceptance|budget|deadline|funding|financing|scope|responsibility|ownership|rights|exit/i.test(dynamicText); const hasRiskOrValueSignal = /风险|收益|价值|能力|案例|履约|可信|控制权|不确定|成本|底线|替代|市场|匹配|risk|value|capability|case|reliable|control|uncertain|cost|floor|alternative|market|fit/i.test(dynamicText); const hasMetricSeparation = metrics.winWin >= 0.52 || metrics.mismatch >= 0.34 || metrics.currentNegotiation >= 0.6 || metrics.fulfillment <= 0.42 || metrics.userUtility >= 0.55 || metrics.counterpartyUtility >= 0.55 || metrics.totalSurplus >= 0.54; return (hasExchangeSignal && hasRiskOrValueSignal) || (coverage >= 0.45 && hasMetricSeparation) || observedOpenFeatures.length >= 4; } function buildComplianceReview(text) { const rules = Object.values(complianceChecklist).flat(); const checklist = rules.map((rule) => { const positive = findEvidence(text, rule.positive); const negative = findEvidence(text, rule.negative); const status = negative.length > 0 ? 'risk' : positive.length > 0 ? 'observed' : 'missing'; return { item: rule.item, status, evidence: [...positive, ...negative].length > 0 ? [...positive, ...negative] : [rule.question], }; }); const missing = checklist.filter((item) => item.status === 'missing').map((item) => item.item); const risks = checklist.filter((item) => item.status === 'risk').map((item) => item.item); const observed = checklist.filter((item) => item.status === 'observed').length; const score = Math.round(((observed + risks.length * 0.35) / checklist.length) * 100); const status = risks.length > 0 ? 'blocking' : missing.length > Math.ceil(checklist.length / 2) ? 'warning' : 'pass'; return { status, score, missing, risks, checklist, }; } export function analyzeContract(text, openFactors = []) { const moduleId = 'universal'; const dynamicFeatures = openFactors.map(normalizeDynamicFactor); const specs = []; // 公式主计算只使用大模型给出的开放参数、权重和证据。 // openFactors 为空代表模型抽取失败;服务端必须直接返回失败。 const features = dynamicFeatures; const specByIdentity = new Map(specs.map((spec) => [`${spec.key}::${spec.label}`, spec])); const specForFeature = (item) => specByIdentity.get(`${item.key}::${item.label}`); const dynamicByKey = new Map(dynamicFeatures.map((feature, index) => [feature.key, openFactors[index]])); const observed = dynamicFeatures.length; const totalAvailable = Math.max(dynamicFeatures.length, 1); const coverage = round(observed / totalAvailable); const missingHighWeightFactors = []; const requiredSpecs = specs.filter((spec) => spec.required); const missingRequiredSpecs = []; const informationSufficiency = buildInformationSufficiency(text, features, specs, coverage); const complianceReview = buildComplianceReview(text); const controlRights = buildControlRights(features, specs); const bayesianReliability = buildBayesianReliability(text, features); const formulaWeights = deriveContractFormulaWeights(features); const factorWeight = (item) => item.weight * (0.7 + item.confidence * 0.3); const reliabilityAdjustment = 0.75 + bayesianReliability.posterior * 0.5; const userUtility = round(weightedAverage(features, (item) => factorWeight(item) * (specForFeature(item)?.userWeight ?? dynamicWeight(dynamicByKey.get(item.key), 'user')))); const counterpartyUtility = round(clamp(weightedAverage(features, (item) => factorWeight(item) * (specForFeature(item)?.otherWeight ?? dynamicWeight(dynamicByKey.get(item.key), 'other'))) * reliabilityAdjustment)); const riskExposure = round(clamp(1 - weightedAverage(features, (item) => factorWeight(item) * (specForFeature(item)?.riskWeight ?? dynamicWeight(dynamicByKey.get(item.key), 'risk'))) + (1 - bayesianReliability.posterior) * 0.12)); const referenceAlignment = round(weightedAverage(features, (item) => factorWeight(item) * (specForFeature(item)?.refWeight ?? dynamicWeight(dynamicByKey.get(item.key), 'ref')))); const informationPenalty = round((1 - Math.min(coverage + dynamicFeatures.length * 0.035, 1)) * 0.18); const expectationGap = round(clamp(1 - referenceAlignment + informationPenalty)); const fairnessReference = round(clamp(0.5 * referenceAlignment + 0.3 * userUtility + 0.2 * counterpartyUtility)); const holdUpRisk = round(clamp(formulaWeights.holdUp.risk * riskExposure + formulaWeights.holdUp.gap * expectationGap + formulaWeights.holdUp.missing * (1 - coverage) + formulaWeights.holdUp.control * controlRights.residualControlRisk + formulaWeights.holdUp.reliability * (1 - bayesianReliability.posterior))); const renegotiationRisk = round(clamp(0.38 * expectationGap + 0.29 * riskExposure + 0.18 * (1 - fairnessReference) + 0.15 * (1 - controlRights.stateContingencyReadiness))); const outsideOptionPressure = round(clamp(1 - Math.abs(userUtility - counterpartyUtility))); const totalSurplus = round((userUtility + counterpartyUtility) / 2); const nashProduct = round(Math.sqrt(Math.max(userUtility, 0) * Math.max(counterpartyUtility, 0))); const matchingEquilibrium = round(clamp(formulaWeights.matching.nash * nashProduct + formulaWeights.matching.reference * referenceAlignment + formulaWeights.matching.surplus * totalSurplus + formulaWeights.matching.outsideOption * outsideOptionPressure + formulaWeights.matching.antiHoldUp * (1 - holdUpRisk) + formulaWeights.matching.control * controlRights.investmentIncentiveFit)); const [winWin, neutral, mismatch] = softmax([ 2.9 * matchingEquilibrium + 1.0 * totalSurplus + 0.58 * coverage + 0.48 * controlRights.controlAlignment + 0.42 * bayesianReliability.posterior - 1.45 * holdUpRisk, 1.2 * coverage + 0.9 * referenceAlignment - 0.3 * Math.abs(userUtility - counterpartyUtility), 2.5 * holdUpRisk + 1.65 * renegotiationRisk + 1.15 * expectationGap - 1.25 * totalSurplus, ]).map((value) => round(value)); const stateUtilities = [ { state: '双赢匹配', probability: winWin, utility: round(1.2 * totalSurplus + 0.5 * matchingEquilibrium) }, { state: '中性波动', probability: neutral, utility: round(0.25 * totalSurplus - 0.1 * renegotiationRisk) }, { state: '错配损耗', probability: mismatch, utility: round(-1 * (0.7 + holdUpRisk + riskExposure)) }, ]; const expectedUtility = round(stateUtilities.reduce((sum, item) => sum + item.probability * item.utility, 0)); const variance = round(stateUtilities.reduce((sum, item) => sum + item.probability * (item.utility - expectedUtility) ** 2, 0)); const cvarLoss = round(mismatch * Math.abs(stateUtilities[2].utility)); const successPanel = buildSuccessPanel({ matchingEquilibrium, totalSurplus, outsideOptionPressure, coverage, holdUpRisk, expectationGap, downsideProbability: mismatch, controlAlignment: controlRights.controlAlignment, stateContingencyReadiness: controlRights.stateContingencyReadiness, reliability: bayesianReliability.posterior, }); const rawScore = Math.round(clamp(formulaWeights.score.winWin * winWin + formulaWeights.score.matching * matchingEquilibrium + formulaWeights.score.reference * referenceAlignment + formulaWeights.score.coverage * coverage + formulaWeights.score.surplus * totalSurplus) * 100); const computableDecisionSignal = hasComputableDecisionSignal(dynamicFeatures, coverage, { winWin, mismatch, currentNegotiation: successPanel.stages.currentNegotiation, fulfillment: successPanel.stages.fulfillment, userUtility, counterpartyUtility, totalSurplus, }); const requiredBlocking = informationSufficiency.blocking && !computableDecisionSignal; const adjustedInformationSufficiency = requiredBlocking ? informationSufficiency : { ...informationSufficiency, blocking: false, level: informationSufficiency.level === 'insufficient' ? 'preliminary' : informationSufficiency.level, reasons: informationSufficiency.blocking ? [...informationSufficiency.reasons, '开放参数已经足以形成当前初步判断,剩余信息用于校验、排序和优化条件'] : informationSufficiency.reasons, }; const score = requiredBlocking ? Math.min(rawScore, 68) : rawScore; const verdict = requiredBlocking ? '必要信息不足,不能下最终结论' : winWin >= 0.55 && mismatch < 0.25 ? '可优化双赢匹配' : mismatch >= 0.38 ? '高风险错配' : '信息不足或中性波动'; const decision = requiredBlocking ? '先补齐价格、时间、当前市场情况、替代选择等必要条件,再做最终验算' : verdict === '可优化双赢匹配' ? '可以继续推进,但必须先把关键利益、责任、控制权和退出条件结构化' : verdict === '高风险错配' ? '暂停按原条件推进,除非关键风险能通过书面条件、分阶段验证或退出机制消除' : '优先补齐高权重缺失因素,再做最终决策'; return { moduleId, moduleName: '全量因子决策引擎', verdict, decision, score, formulas: { generalizedDistribution: 'F(U)=Σ p_s·1(U_s<=u); E[U]=Σp_s·U_s; Var(U)=Σp_s·(U_s-E[U])²; CVaR_loss=P(loss)·|U_loss|', referenceContract: 'A_ref=Σ(w_ref_i·v_i·conf_i)/Σw_ref_i; Gap=1-A_ref+InfoPenalty; HoldUp=0.52·Risk+0.28·Gap+0.20·Missing', bilateralMatching: 'M=0.36·sqrt(U_user·U_other)+0.24·A_ref+0.18·Surplus+0.12·OutsideOption+0.10·(1-HoldUp)', }, features, factorCoverage: { total: totalAvailable, observed, coverage, missingHighWeightFactors, openFactorCount: dynamicFeatures.length, }, requiredFactors: { total: requiredSpecs.length, observed: requiredSpecs.length - missingRequiredSpecs.length, missing: missingRequiredSpecs.map((spec) => spec.label), blocking: requiredBlocking, }, informationSufficiency: adjustedInformationSufficiency, complianceReview, distribution: { winWin, neutral, mismatch }, successPanel, generalizedDistribution: { expectedUtility, variance, downsideProbability: mismatch, cvarLoss, stateUtilities, }, referenceContract: { referenceAlignment, expectationGap, holdUpRisk, renegotiationRisk, fairnessReference, }, controlRights, bayesianReliability, bilateralMatching: { userUtility, counterpartyUtility, outsideOptionPressure, totalSurplus, nashProduct, matchingEquilibrium, }, missingQuestions: buildQuestions(features, specs), modelPayload: { engineVersion: 'contract-engine-universal-factor-matrix-1.1', theoryBasis: ['广义分布函数模型', '不完全参照点契约', '双边匹配均衡', '贝叶斯后验更新', '开放式不定参数矩阵'], constraints: ['大模型只能解释和表达计算结果', '不得替代后台概率分布自行主观判断', '信息不足时必须优先追问高权重缺失因素'], }, }; }