binbin888's picture
Deploy v0.5.124 single candidate utility display
9a61dc3 verified
Raw
History Blame Contribute Delete
44.2 kB
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: ['大模型只能解释和表达计算结果', '不得替代后台概率分布自行主观判断', '信息不足时必须优先追问高权重缺失因素'],
},
};
}